mirror of
https://github.com/UnickSoft/graphonline.git
synced 2026-04-19 14:42:45 +00:00
first commit
This commit is contained in:
22
lib/BackUrl.php
Executable file
22
lib/BackUrl.php
Executable file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс для запомининия прошло url
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class BackUrl
|
||||
{
|
||||
public $flashParam; // Я знаю что public плохо, но мне все равно
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->flashParam = new FlashParam();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->flashParam->Set('back_url', GetCurUrl());
|
||||
}
|
||||
};
|
||||
?>
|
||||
1030
lib/CI_Upload.php
Executable file
1030
lib/CI_Upload.php
Executable file
File diff suppressed because it is too large
Load Diff
79
lib/Db/Db.php
Executable file
79
lib/Db/Db.php
Executable file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс инициализации баз данных
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class Db
|
||||
{
|
||||
// Обработчик ошибок БД
|
||||
public static function DbSimpleError($message, $info)
|
||||
{
|
||||
global $g_config;
|
||||
|
||||
if (!error_reporting())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ($info['query'] == "mysql_connect()") // Не подсоединилась к БД
|
||||
{
|
||||
exit("Can not connect to database(s)"); // Не работаем дальше с кодом
|
||||
}
|
||||
|
||||
static $fileLogger = NULL;
|
||||
if (is_null($fileLogger))
|
||||
{
|
||||
$fileLogger = FileLogger::Create($g_config['dbSimple']['dbLogFile']);
|
||||
}
|
||||
$fileLogger->Error(
|
||||
PHP_EOL .
|
||||
"\tquery: " . $info['query'] . PHP_EOL .
|
||||
"\tmessage: " . $info['message'] . PHP_EOL .
|
||||
"\tcode: " . $info['code'] . PHP_EOL .
|
||||
"\tcontext: " . $info['context'] . PHP_EOL .
|
||||
PHP_EOL
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $g_config;
|
||||
|
||||
// Подключаем модули для работы с DbSimple (не по подгрузится автолоудером)
|
||||
$path = dirname(__FILE__) . '/DbSimple/';
|
||||
require_once $path . 'Generic.php';
|
||||
require_once $path . 'Mysql.php';
|
||||
require_once $path . 'Postgresql.php';
|
||||
require_once $path . 'my/MyDataBaseLog.php';
|
||||
|
||||
$dbs = $g_config['dbSimple']['databases'];
|
||||
|
||||
// Собираем все объекты в $o
|
||||
$o = new stdClass();
|
||||
foreach ($dbs as $db => $conn)
|
||||
{
|
||||
$dsn = $conn['dsn'];
|
||||
$cacheFunc = isset($conn['pCacheFunc']) ? $conn['pCacheFunc'] : NULL;
|
||||
|
||||
$o->$db = DbSimple_Generic::connect($dsn);
|
||||
|
||||
if ($g_config['dbSimple']['logDbError'])
|
||||
{
|
||||
MyDataBaseLog::SetFuncOnError(array(__CLASS__, 'DbSimpleError'));
|
||||
$o->$db->setLogger(array('MyDataBaseLog', 'Log'));
|
||||
$o->$db->setErrorHandler(array('MyDataBaseLog', 'Error'));
|
||||
}
|
||||
|
||||
if ($cacheFunc)
|
||||
{
|
||||
$o->$db->setCacher($cacheFunc);
|
||||
}
|
||||
}
|
||||
|
||||
// Регистрируем все базы данных как объект $g_databases
|
||||
$GLOBALS['g_databases'] = $o;
|
||||
}
|
||||
};
|
||||
?>
|
||||
1361
lib/Db/DbSimple/Generic.php
Executable file
1361
lib/Db/DbSimple/Generic.php
Executable file
File diff suppressed because it is too large
Load Diff
290
lib/Db/DbSimple/Ibase.php
Executable file
290
lib/Db/DbSimple/Ibase.php
Executable file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/**
|
||||
* DbSimple_Ibase: Interbase/Firebird database.
|
||||
* (C) Dk Lab, http://en.dklab.ru
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
* See http://www.gnu.org/copyleft/lesser.html
|
||||
*
|
||||
* Placeholders are emulated because of logging purposes.
|
||||
*
|
||||
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/
|
||||
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/
|
||||
*
|
||||
* @version 2.x $Id: Ibase.php 221 2007-07-27 23:24:35Z dk $
|
||||
*/
|
||||
require_once dirname(__FILE__) . '/Generic.php';
|
||||
|
||||
/**
|
||||
* Best transaction parameters for script queries.
|
||||
* They never give us update conflicts (unlike others)!
|
||||
* Used by default.
|
||||
*/
|
||||
define('IBASE_BEST_TRANSACTION', IBASE_COMMITTED + IBASE_WAIT + IBASE_REC_VERSION);
|
||||
define('IBASE_BEST_FETCH', IBASE_UNIXTIME);
|
||||
|
||||
/**
|
||||
* Database class for Interbase/Firebird.
|
||||
*/
|
||||
|
||||
class DbSimple_Ibase extends DbSimple_Generic_Database
|
||||
{
|
||||
var $DbSimple_Ibase_BEST_TRANSACTION = IBASE_BEST_TRANSACTION;
|
||||
var $DbSimple_Ibase_USE_NATIVE_PHOLDERS = true;
|
||||
var $fetchFlags = IBASE_BEST_FETCH;
|
||||
var $link;
|
||||
var $trans;
|
||||
var $prepareCache = array();
|
||||
|
||||
/**
|
||||
* constructor(string $dsn)
|
||||
* Connect to Interbase/Firebird.
|
||||
*/
|
||||
function DbSimple_Ibase($dsn)
|
||||
{
|
||||
$p = DbSimple_Generic::parseDSN($dsn);
|
||||
if (!is_callable('ibase_connect')) {
|
||||
return $this->_setLastError("-1", "Interbase/Firebird extension is not loaded", "ibase_connect");
|
||||
}
|
||||
$ok = $this->link = ibase_connect(
|
||||
$p['host'] . (empty($p['port'])? "" : ":".$p['port']) .':'.preg_replace('{^/}s', '', $p['path']),
|
||||
$p['user'],
|
||||
$p['pass'],
|
||||
isset($p['CHARSET']) ? $p['CHARSET'] : 'win1251',
|
||||
isset($p['BUFFERS']) ? $p['BUFFERS'] : 0,
|
||||
isset($p['DIALECT']) ? $p['DIALECT'] : 3,
|
||||
isset($p['ROLE']) ? $p['ROLE'] : ''
|
||||
);
|
||||
if (isset($p['TRANSACTION'])) $this->DbSimple_Ibase_BEST_TRANSACTION = eval($p['TRANSACTION'].";");
|
||||
$this->_resetLastError();
|
||||
if (!$ok) return $this->_setDbError('ibase_connect()');
|
||||
}
|
||||
|
||||
function _performEscape($s, $isIdent=false)
|
||||
{
|
||||
if (!$isIdent)
|
||||
return "'" . str_replace("'", "''", $s) . "'";
|
||||
else
|
||||
return '"' . str_replace('"', '_', $s) . '"';
|
||||
}
|
||||
|
||||
function _performTransaction($parameters=null)
|
||||
{
|
||||
if ($parameters === null) $parameters = $this->DbSimple_Ibase_BEST_TRANSACTION;
|
||||
$this->trans = @ibase_trans($parameters, $this->link);
|
||||
}
|
||||
|
||||
function& _performNewBlob($blobid=null)
|
||||
{
|
||||
$obj =& new DbSimple_Ibase_Blob($this, $blobid);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
function _performGetBlobFieldNames($result)
|
||||
{
|
||||
$blobFields = array();
|
||||
for ($i=ibase_num_fields($result)-1; $i>=0; $i--) {
|
||||
$info = ibase_field_info($result, $i);
|
||||
if ($info['type'] === "BLOB") $blobFields[] = $info['name'];
|
||||
}
|
||||
return $blobFields;
|
||||
}
|
||||
|
||||
function _performGetPlaceholderIgnoreRe()
|
||||
{
|
||||
return '
|
||||
" (?> [^"\\\\]+|\\\\"|\\\\)* " |
|
||||
\' (?> [^\'\\\\]+|\\\\\'|\\\\)* \' |
|
||||
` (?> [^`]+ | ``)* ` | # backticks
|
||||
/\* .*? \*/ # comments
|
||||
';
|
||||
}
|
||||
|
||||
function _performCommit()
|
||||
{
|
||||
if (!is_resource($this->trans)) return false;
|
||||
$result = @ibase_commit($this->trans);
|
||||
if (true === $result) {
|
||||
$this->trans = null;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
function _performRollback()
|
||||
{
|
||||
if (!is_resource($this->trans)) return false;
|
||||
$result = @ibase_rollback($this->trans);
|
||||
if (true === $result) {
|
||||
$this->trans = null;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function _performTransformQuery(&$queryMain, $how)
|
||||
{
|
||||
// If we also need to calculate total number of found rows...
|
||||
switch ($how) {
|
||||
// Prepare total calculation (if possible)
|
||||
case 'CALC_TOTAL':
|
||||
// Not possible
|
||||
return true;
|
||||
|
||||
// Perform total calculation.
|
||||
case 'GET_TOTAL':
|
||||
// XTODO: GROUP BY ... -> COUNT(DISTINCT ...)
|
||||
$re = '/^
|
||||
(?> -- [^\r\n]* | \s+)*
|
||||
(\s* SELECT \s+) #1
|
||||
((?:FIRST \s+ \S+ \s+ (?:SKIP \s+ \S+ \s+)? )?) #2
|
||||
(.*?) #3
|
||||
(\s+ FROM \s+ .*?) #4
|
||||
((?:\s+ ORDER \s+ BY \s+ .*)?) #5
|
||||
$/six';
|
||||
$m = null;
|
||||
if (preg_match($re, $queryMain[0], $m)) {
|
||||
$queryMain[0] = $m[1] . $this->_fieldList2Count($m[3]) . " AS C" . $m[4];
|
||||
$skipHead = substr_count($m[2], '?');
|
||||
if ($skipHead) array_splice($queryMain, 1, $skipHead);
|
||||
$skipTail = substr_count($m[5], '?');
|
||||
if ($skipTail) array_splice($queryMain, -$skipTail);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _performQuery($queryMain)
|
||||
{
|
||||
$this->_lastQuery = $queryMain;
|
||||
$this->_expandPlaceholders($queryMain, $this->DbSimple_Ibase_USE_NATIVE_PHOLDERS);
|
||||
|
||||
$hash = $queryMain[0];
|
||||
|
||||
if (!isset($this->prepareCache[$hash])) {
|
||||
$this->prepareCache[$hash] = @ibase_prepare((is_resource($this->trans) ? $this->trans : $this->link), $queryMain[0]);
|
||||
} else {
|
||||
// Prepare cache hit!
|
||||
}
|
||||
|
||||
$prepared = $this->prepareCache[$hash];
|
||||
if (!$prepared) return $this->_setDbError($queryMain[0]);
|
||||
$queryMain[0] = $prepared;
|
||||
$result = @call_user_func_array('ibase_execute', $queryMain);
|
||||
// ATTENTION!!!
|
||||
// WE MUST save prepared ID (stored in $prepared variable) somewhere
|
||||
// before returning $result because of ibase destructor. Now it is done
|
||||
// by $this->prepareCache. When variable $prepared goes out of scope, it
|
||||
// is destroyed, and memory for result also freed by PHP. Totally we
|
||||
// got "Invalud statement handle" error message.
|
||||
|
||||
if ($result === false) return $this->_setDbError($queryMain[0]);
|
||||
if (!is_resource($result)) {
|
||||
// Non-SELECT queries return number of affected rows, SELECT - resource.
|
||||
return @ibase_affected_rows((is_resource($this->trans) ? $this->trans : $this->link));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function _performFetch($result)
|
||||
{
|
||||
// Select fetch mode.
|
||||
$flags = $this->fetchFlags;
|
||||
if (empty($this->attributes['BLOB_OBJ'])) $flags = $flags | IBASE_TEXT;
|
||||
else $flags = $flags & ~IBASE_TEXT;
|
||||
|
||||
$row = @ibase_fetch_assoc($result, $flags);
|
||||
if (ibase_errmsg()) return $this->_setDbError($this->_lastQuery);
|
||||
if ($row === false) return null;
|
||||
return $row;
|
||||
}
|
||||
|
||||
|
||||
function _setDbError($query)
|
||||
{
|
||||
return $this->_setLastError(ibase_errcode(), ibase_errmsg(), $query);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DbSimple_Ibase_Blob extends DbSimple_Generic_Blob
|
||||
{
|
||||
var $blob; // resourse link
|
||||
var $id;
|
||||
var $database;
|
||||
|
||||
function DbSimple_Ibase_Blob(&$database, $id=null)
|
||||
{
|
||||
$this->database =& $database;
|
||||
$this->id = $id;
|
||||
$this->blob = null;
|
||||
}
|
||||
|
||||
function read($len)
|
||||
{
|
||||
if ($this->id === false) return ''; // wr-only blob
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
$data = @ibase_blob_get($this->blob, $len);
|
||||
if ($data === false) return $this->_setDbError('read');
|
||||
return $data;
|
||||
}
|
||||
|
||||
function write($data)
|
||||
{
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
$ok = @ibase_blob_add($this->blob, $data);
|
||||
if ($ok === false) return $this->_setDbError('add data to');
|
||||
return true;
|
||||
}
|
||||
|
||||
function close()
|
||||
{
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
if ($this->blob) {
|
||||
$id = @ibase_blob_close($this->blob);
|
||||
if ($id === false) return $this->_setDbError('close');
|
||||
$this->blob = null;
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
return $this->id ? $this->id : $id;
|
||||
}
|
||||
|
||||
function length()
|
||||
{
|
||||
if ($this->id === false) return 0; // wr-only blob
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
$info = @ibase_blob_info($this->id);
|
||||
if (!$info) return $this->_setDbError('get length of');
|
||||
return $info[0];
|
||||
}
|
||||
|
||||
function _setDbError($query)
|
||||
{
|
||||
$hId = $this->id === null ? "null" : ($this->id === false ? "false" : $this->id);
|
||||
$query = "-- $query BLOB $hId";
|
||||
$this->database->_setDbError($query);
|
||||
}
|
||||
|
||||
// Called on each blob use (reading or writing).
|
||||
function _firstUse()
|
||||
{
|
||||
// BLOB is opened - nothing to do.
|
||||
if (is_resource($this->blob)) return true;
|
||||
// Open or create blob.
|
||||
if ($this->id !== null) {
|
||||
$this->blob = @ibase_blob_open($this->id);
|
||||
if ($this->blob === false) return $this->_setDbError('open');
|
||||
} else {
|
||||
$this->blob = @ibase_blob_create($this->database->link);
|
||||
if ($this->blob === false) return $this->_setDbError('create');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
230
lib/Db/DbSimple/Mysql.php
Executable file
230
lib/Db/DbSimple/Mysql.php
Executable file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* DbSimple_Mysql: MySQL database.
|
||||
* (C) Dk Lab, http://en.dklab.ru
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
* See http://www.gnu.org/copyleft/lesser.html
|
||||
*
|
||||
* Placeholders end blobs are emulated.
|
||||
*
|
||||
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/
|
||||
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/
|
||||
*
|
||||
* @version 2.x $Id: Mysql.php 247 2008-08-18 21:17:08Z dk $
|
||||
*/
|
||||
require_once dirname(__FILE__) . '/Generic.php';
|
||||
|
||||
|
||||
/**
|
||||
* Database class for MySQL.
|
||||
*/
|
||||
class DbSimple_Mysql extends DbSimple_Generic_Database
|
||||
{
|
||||
var $link;
|
||||
|
||||
/**
|
||||
* constructor(string $dsn)
|
||||
* Connect to MySQL.
|
||||
*/
|
||||
function DbSimple_Mysql($dsn)
|
||||
{
|
||||
$p = DbSimple_Generic::parseDSN($dsn);
|
||||
if (!is_callable('mysql_connect')) {
|
||||
return $this->_setLastError("-1", "MySQL extension is not loaded", "mysql_connect");
|
||||
}
|
||||
$ok = $this->link = @mysql_connect(
|
||||
$p['host'] . (empty($p['port'])? "" : ":".$p['port']),
|
||||
$p['user'],
|
||||
$p['pass'],
|
||||
true
|
||||
);
|
||||
$this->_resetLastError();
|
||||
if (!$ok) return $this->_setDbError('mysql_connect()');
|
||||
$ok = @mysql_select_db(preg_replace('{^/}s', '', $p['path']), $this->link);
|
||||
if (!$ok) return $this->_setDbError('mysql_select_db()');
|
||||
if (isset($p["charset"])) {
|
||||
$this->query('SET NAMES ?', $p["charset"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _performEscape($s, $isIdent=false)
|
||||
{
|
||||
if (!$isIdent) {
|
||||
return "'" . mysql_real_escape_string($s, $this->link) . "'";
|
||||
} else {
|
||||
return "`" . str_replace('`', '``', $s) . "`";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _performTransaction($parameters=null)
|
||||
{
|
||||
return $this->query('BEGIN');
|
||||
}
|
||||
|
||||
|
||||
function& _performNewBlob($blobid=null)
|
||||
{
|
||||
$obj =& new DbSimple_Mysql_Blob($this, $blobid);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
||||
function _performGetBlobFieldNames($result)
|
||||
{
|
||||
$blobFields = array();
|
||||
for ($i=mysql_num_fields($result)-1; $i>=0; $i--) {
|
||||
$type = mysql_field_type($result, $i);
|
||||
if (strpos($type, "BLOB") !== false) $blobFields[] = mysql_field_name($result, $i);
|
||||
}
|
||||
return $blobFields;
|
||||
}
|
||||
|
||||
|
||||
function _performGetPlaceholderIgnoreRe()
|
||||
{
|
||||
return '
|
||||
" (?> [^"\\\\]+|\\\\"|\\\\)* " |
|
||||
\' (?> [^\'\\\\]+|\\\\\'|\\\\)* \' |
|
||||
` (?> [^`]+ | ``)* ` | # backticks
|
||||
/\* .*? \*/ # comments
|
||||
';
|
||||
}
|
||||
|
||||
|
||||
function _performCommit()
|
||||
{
|
||||
return $this->query('COMMIT');
|
||||
}
|
||||
|
||||
|
||||
function _performRollback()
|
||||
{
|
||||
return $this->query('ROLLBACK');
|
||||
}
|
||||
|
||||
|
||||
function _performTransformQuery(&$queryMain, $how)
|
||||
{
|
||||
// If we also need to calculate total number of found rows...
|
||||
switch ($how) {
|
||||
// Prepare total calculation (if possible)
|
||||
case 'CALC_TOTAL':
|
||||
$m = null;
|
||||
if (preg_match('/^(\s* SELECT)(.*)/six', $queryMain[0], $m)) {
|
||||
if ($this->_calcFoundRowsAvailable()) {
|
||||
$queryMain[0] = $m[1] . ' SQL_CALC_FOUND_ROWS' . $m[2];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
// Perform total calculation.
|
||||
case 'GET_TOTAL':
|
||||
// Built-in calculation available?
|
||||
if ($this->_calcFoundRowsAvailable()) {
|
||||
$queryMain = array('SELECT FOUND_ROWS()');
|
||||
}
|
||||
// Else use manual calculation.
|
||||
// XTODO: GROUP BY ... -> COUNT(DISTINCT ...)
|
||||
$re = '/^
|
||||
(?> -- [^\r\n]* | \s+)*
|
||||
(\s* SELECT \s+) #1
|
||||
(.*?) #2
|
||||
(\s+ FROM \s+ .*?) #3
|
||||
((?:\s+ ORDER \s+ BY \s+ .*?)?) #4
|
||||
((?:\s+ LIMIT \s+ \S+ \s* (?:, \s* \S+ \s*)? )?) #5
|
||||
$/six';
|
||||
$m = null;
|
||||
if (preg_match($re, $queryMain[0], $m)) {
|
||||
$query[0] = $m[1] . $this->_fieldList2Count($m[2]) . " AS C" . $m[3];
|
||||
$skipTail = substr_count($m[4] . $m[5], '?');
|
||||
if ($skipTail) array_splice($query, -$skipTail);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function _performQuery($queryMain)
|
||||
{
|
||||
$this->_lastQuery = $queryMain;
|
||||
$this->_expandPlaceholders($queryMain, false);
|
||||
$result = @mysql_query($queryMain[0], $this->link);
|
||||
if ($result === false) return $this->_setDbError($queryMain[0]);
|
||||
if (!is_resource($result)) {
|
||||
if (preg_match('/^\s* INSERT \s+/six', $queryMain[0])) {
|
||||
// INSERT queries return generated ID.
|
||||
return @mysql_insert_id($this->link);
|
||||
}
|
||||
// Non-SELECT queries return number of affected rows, SELECT - resource.
|
||||
return @mysql_affected_rows($this->link);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
function _performFetch($result)
|
||||
{
|
||||
$row = @mysql_fetch_assoc($result);
|
||||
if (mysql_error()) return $this->_setDbError($this->_lastQuery);
|
||||
if ($row === false) return null;
|
||||
return $row;
|
||||
}
|
||||
|
||||
|
||||
function _setDbError($query)
|
||||
{
|
||||
return $this->_setLastError(mysql_errno($this->link), mysql_error($this->link), $query);
|
||||
}
|
||||
|
||||
|
||||
function _calcFoundRowsAvailable()
|
||||
{
|
||||
$ok = version_compare(mysql_get_server_info($this->link), '4.0') >= 0;
|
||||
return $ok;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DbSimple_Mysql_Blob extends DbSimple_Generic_Blob
|
||||
{
|
||||
// MySQL does not support separate BLOB fetching.
|
||||
var $blobdata = null;
|
||||
var $curSeek = 0;
|
||||
|
||||
function DbSimple_Mysql_Blob(&$database, $blobdata=null)
|
||||
{
|
||||
$this->blobdata = $blobdata;
|
||||
$this->curSeek = 0;
|
||||
}
|
||||
|
||||
function read($len)
|
||||
{
|
||||
$p = $this->curSeek;
|
||||
$this->curSeek = min($this->curSeek + $len, strlen($this->blobdata));
|
||||
return substr($this->blobdata, $this->curSeek, $len);
|
||||
}
|
||||
|
||||
function write($data)
|
||||
{
|
||||
$this->blobdata .= $data;
|
||||
}
|
||||
|
||||
function close()
|
||||
{
|
||||
return $this->blobdata;
|
||||
}
|
||||
|
||||
function length()
|
||||
{
|
||||
return strlen($this->blobdata);
|
||||
}
|
||||
}
|
||||
?>
|
||||
312
lib/Db/DbSimple/Postgresql.php
Executable file
312
lib/Db/DbSimple/Postgresql.php
Executable file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
/**
|
||||
* DbSimple_Postgreql: PostgreSQL database.
|
||||
* (C) Dk Lab, http://en.dklab.ru
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
* See http://www.gnu.org/copyleft/lesser.html
|
||||
*
|
||||
* Placeholders are emulated because of logging purposes.
|
||||
*
|
||||
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/
|
||||
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/
|
||||
*
|
||||
* @version 2.x $Id: Postgresql.php 167 2007-01-22 10:12:09Z tit $
|
||||
*/
|
||||
require_once dirname(__FILE__) . '/Generic.php';
|
||||
|
||||
|
||||
/**
|
||||
* Database class for PostgreSQL.
|
||||
*/
|
||||
class DbSimple_Postgresql extends DbSimple_Generic_Database
|
||||
{
|
||||
|
||||
var $DbSimple_Postgresql_USE_NATIVE_PHOLDERS = null;
|
||||
var $prepareCache = array();
|
||||
var $link;
|
||||
|
||||
/**
|
||||
* constructor(string $dsn)
|
||||
* Connect to PostgresSQL.
|
||||
*/
|
||||
function DbSimple_Postgresql($dsn)
|
||||
{
|
||||
$p = DbSimple_Generic::parseDSN($dsn);
|
||||
if (!is_callable('pg_connect')) {
|
||||
return $this->_setLastError("-1", "PostgreSQL extension is not loaded", "pg_connect");
|
||||
}
|
||||
|
||||
// Prepare+execute works only in PHP 5.1+.
|
||||
$this->DbSimple_Postgresql_USE_NATIVE_PHOLDERS = function_exists('pg_prepare');
|
||||
|
||||
$ok = $this->link = @pg_connect(
|
||||
$t = (!empty($p['host']) ? 'host='.$p['host'].' ' : '').
|
||||
(!empty($p['port']) ? 'port='.$p['port'].' ' : '').
|
||||
'dbname='.preg_replace('{^/}s', '', $p['path']).' '.
|
||||
(!empty($p['user']) ? 'user='.$p['user'].' ' : '').
|
||||
(!empty($p['pass']) ? 'password='.$p['pass'].' ' : '')
|
||||
);
|
||||
$this->_resetLastError();
|
||||
if (!$ok) return $this->_setDbError('pg_connect()');
|
||||
}
|
||||
|
||||
|
||||
function _performEscape($s, $isIdent=false)
|
||||
{
|
||||
if (!$isIdent)
|
||||
return "'" . str_replace("'", "''", $s) . "'";
|
||||
else
|
||||
return '"' . str_replace('"', '_', $s) . '"';
|
||||
}
|
||||
|
||||
|
||||
function _performTransaction($parameters=null)
|
||||
{
|
||||
return $this->query('BEGIN');
|
||||
}
|
||||
|
||||
|
||||
function& _performNewBlob($blobid=null)
|
||||
{
|
||||
$obj =& new DbSimple_Postgresql_Blob($this, $blobid);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
||||
function _performGetBlobFieldNames($result)
|
||||
{
|
||||
$blobFields = array();
|
||||
for ($i=pg_num_fields($result)-1; $i>=0; $i--) {
|
||||
$type = pg_field_type($result, $i);
|
||||
if (strpos($type, "BLOB") !== false) $blobFields[] = pg_field_name($result, $i);
|
||||
}
|
||||
return $blobFields;
|
||||
}
|
||||
|
||||
// XTODO: Real PostgreSQL escape
|
||||
function _performGetPlaceholderIgnoreRe()
|
||||
{
|
||||
return '
|
||||
" (?> [^"\\\\]+|\\\\"|\\\\)* " |
|
||||
\' (?> [^\'\\\\]+|\\\\\'|\\\\)* \' |
|
||||
/\* .*? \*/ # comments
|
||||
';
|
||||
}
|
||||
|
||||
function _performGetNativePlaceholderMarker($n)
|
||||
{
|
||||
// PostgreSQL uses specific placeholders such as $1, $2, etc.
|
||||
return '$' . ($n + 1);
|
||||
}
|
||||
|
||||
function _performCommit()
|
||||
{
|
||||
return $this->query('COMMIT');
|
||||
}
|
||||
|
||||
|
||||
function _performRollback()
|
||||
{
|
||||
return $this->query('ROLLBACK');
|
||||
}
|
||||
|
||||
|
||||
function _performTransformQuery(&$queryMain, $how)
|
||||
{
|
||||
|
||||
// If we also need to calculate total number of found rows...
|
||||
switch ($how) {
|
||||
// Prepare total calculation (if possible)
|
||||
case 'CALC_TOTAL':
|
||||
// Not possible
|
||||
return true;
|
||||
|
||||
// Perform total calculation.
|
||||
case 'GET_TOTAL':
|
||||
// XTODO: GROUP BY ... -> COUNT(DISTINCT ...)
|
||||
$re = '/^
|
||||
(?> -- [^\r\n]* | \s+)*
|
||||
(\s* SELECT \s+) #1
|
||||
(.*?) #2
|
||||
(\s+ FROM \s+ .*?) #3
|
||||
((?:\s+ ORDER \s+ BY \s+ .*?)?) #4
|
||||
((?:\s+ LIMIT \s+ \S+ \s* (?: OFFSET \s* \S+ \s*)? )?) #5
|
||||
$/six';
|
||||
$m = null;
|
||||
if (preg_match($re, $queryMain[0], $m)) {
|
||||
$queryMain[0] = $m[1] . $this->_fieldList2Count($m[2]) . " AS C" . $m[3];
|
||||
$skipTail = substr_count($m[4] . $m[5], '?');
|
||||
if ($skipTail) array_splice($queryMain, -$skipTail);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function _performQuery($queryMain)
|
||||
{
|
||||
$this->_lastQuery = $queryMain;
|
||||
$isInsert = preg_match('/^\s* INSERT \s+/six', $queryMain[0]);
|
||||
|
||||
//
|
||||
// Note that in case of INSERT query we CANNOT work with prepare...execute
|
||||
// cache, because RULEs do not work after pg_execute(). This is a very strange
|
||||
// bug... To reproduce:
|
||||
// $DB->query("CREATE TABLE test(id SERIAL, str VARCHAR(10)) WITH OIDS");
|
||||
// $DB->query("CREATE RULE test_r AS ON INSERT TO test DO (SELECT 111 AS id)");
|
||||
// print_r($DB->query("INSERT INTO test(str) VALUES ('test')"));
|
||||
// In case INSERT + pg_execute() it returns new row OID (numeric) instead
|
||||
// of result of RULE query. Strange, very strange...
|
||||
//
|
||||
|
||||
if ($this->DbSimple_Postgresql_USE_NATIVE_PHOLDERS && !$isInsert) {
|
||||
// Use native placeholders only if PG supports them.
|
||||
$this->_expandPlaceholders($queryMain, true);
|
||||
$hash = md5($queryMain[0]);
|
||||
if (!isset($this->prepareCache[$hash])) {
|
||||
$this->prepareCache[$hash] = true;
|
||||
$prepared = @pg_prepare($this->link, $hash, $queryMain[0]);
|
||||
if ($prepared === false) return $this->_setDbError($queryMain[0]);
|
||||
} else {
|
||||
// Prepare cache hit!
|
||||
}
|
||||
$result = pg_execute($this->link, $hash, array_slice($queryMain, 1));
|
||||
} else {
|
||||
// No support for native placeholders or INSERT query.
|
||||
$this->_expandPlaceholders($queryMain, false);
|
||||
$result = @pg_query($this->link, $queryMain[0]);
|
||||
}
|
||||
|
||||
if ($result === false) return $this->_setDbError($queryMain);
|
||||
if (!pg_num_fields($result)) {
|
||||
if ($isInsert) {
|
||||
// INSERT queries return generated OID (if table is WITH OIDs).
|
||||
//
|
||||
// Please note that unfortunately we cannot use lastval() PostgreSQL
|
||||
// stored function because it generates fatal error if INSERT query
|
||||
// does not contain sequence-based field at all. This error terminates
|
||||
// the current transaction, and we cannot continue to work nor know
|
||||
// if table contains sequence-updateable field or not.
|
||||
//
|
||||
// To use auto-increment functionality you must invoke
|
||||
// $insertedId = $DB->query("SELECT lastval()")
|
||||
// manually where it is really needed.
|
||||
//
|
||||
return @pg_last_oid($result);
|
||||
}
|
||||
// Non-SELECT queries return number of affected rows, SELECT - resource.
|
||||
return @pg_affected_rows($result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
function _performFetch($result)
|
||||
{
|
||||
$row = @pg_fetch_assoc($result);
|
||||
if (pg_last_error($this->link)) return $this->_setDbError($this->_lastQuery);
|
||||
if ($row === false) return null;
|
||||
return $row;
|
||||
}
|
||||
|
||||
|
||||
function _setDbError($query)
|
||||
{
|
||||
return $this->_setLastError(null, pg_last_error($this->link), $query);
|
||||
}
|
||||
|
||||
function _getVersion()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DbSimple_Postgresql_Blob extends DbSimple_Generic_Blob
|
||||
{
|
||||
var $blob; // resourse link
|
||||
var $id;
|
||||
var $database;
|
||||
|
||||
function DbSimple_Postgresql_Blob(&$database, $id=null)
|
||||
{
|
||||
$this->database =& $database;
|
||||
$this->database->transaction();
|
||||
$this->id = $id;
|
||||
$this->blob = null;
|
||||
}
|
||||
|
||||
function read($len)
|
||||
{
|
||||
if ($this->id === false) return ''; // wr-only blob
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
$data = @pg_lo_read($this->blob, $len);
|
||||
if ($data === false) return $this->_setDbError('read');
|
||||
return $data;
|
||||
}
|
||||
|
||||
function write($data)
|
||||
{
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
$ok = @pg_lo_write($this->blob, $data);
|
||||
if ($ok === false) return $this->_setDbError('add data to');
|
||||
return true;
|
||||
}
|
||||
|
||||
function close()
|
||||
{
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
if ($this->blob) {
|
||||
$id = @pg_lo_close($this->blob);
|
||||
if ($id === false) return $this->_setDbError('close');
|
||||
$this->blob = null;
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
$this->database->commit();
|
||||
return $this->id? $this->id : $id;
|
||||
}
|
||||
|
||||
function length()
|
||||
{
|
||||
if (!($e=$this->_firstUse())) return $e;
|
||||
|
||||
@pg_lo_seek($this->blob, 0, PGSQL_SEEK_END);
|
||||
$len = @pg_lo_tell($this->blob);
|
||||
@pg_lo_seek($this->blob, 0, PGSQL_SEEK_SET);
|
||||
|
||||
if (!$len) return $this->_setDbError('get length of');
|
||||
return $len;
|
||||
}
|
||||
|
||||
function _setDbError($query)
|
||||
{
|
||||
$hId = $this->id === null? "null" : ($this->id === false? "false" : $this->id);
|
||||
$query = "-- $query BLOB $hId";
|
||||
$this->database->_setDbError($query);
|
||||
}
|
||||
|
||||
// Called on each blob use (reading or writing).
|
||||
function _firstUse()
|
||||
{
|
||||
// BLOB opened - do nothing.
|
||||
if (is_resource($this->blob)) return true;
|
||||
|
||||
// Open or create blob.
|
||||
if ($this->id !== null) {
|
||||
$this->blob = @pg_lo_open($this->database->link, $this->id, 'rw');
|
||||
if ($this->blob === false) return $this->_setDbError('open');
|
||||
} else {
|
||||
$this->id = @pg_lo_create($this->database->link);
|
||||
$this->blob = @pg_lo_open($this->database->link, $this->id, 'w');
|
||||
if ($this->blob === false) return $this->_setDbError('create');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
112
lib/Db/DbSimple/my/MyDataBaseLog.php
Executable file
112
lib/Db/DbSimple/my/MyDataBaseLog.php
Executable file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Для вывода лога запросов к БД (использовать совместно с DbSimple)
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class MyDataBaseLog
|
||||
{
|
||||
private static $dbLog = array();
|
||||
private static $pFuncOnError = NULL;
|
||||
|
||||
/**
|
||||
* Устанавливает функцию на ошибку запроса
|
||||
*/
|
||||
public static function SetFuncOnError($func)
|
||||
{
|
||||
self::$pFuncOnError = $func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Определить функцию на получение ошибки в $info наиболее полезные параметры [query, message, code, context]
|
||||
*/
|
||||
public static function Error($message, $info)
|
||||
{
|
||||
if (!error_reporting())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty(self::$pFuncOnError))
|
||||
{
|
||||
call_user_func(self::$pFuncOnError, $message, $info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция логирования запроса
|
||||
*/
|
||||
public static function Log($db, $sql)
|
||||
{
|
||||
$caller = $db->findLibraryCaller();
|
||||
$log = array();
|
||||
$log['q'] = $sql;
|
||||
$log['file'] = $caller['file'];
|
||||
$log['line'] = $caller['line'];
|
||||
$log['time'] = $caller['object']->_statistics['time'];
|
||||
$log['count'] = $caller['object']->_statistics['count'];
|
||||
$log['error'] = $caller['object']->error;
|
||||
$log['errorMsg'] = $caller['object']->errmsg;
|
||||
|
||||
self::$dbLog[] = $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Показывает лог-табличку
|
||||
*/
|
||||
public static function Render()
|
||||
{
|
||||
ob_start();
|
||||
?>
|
||||
<table id='iDbLogger' cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<th>№</th>
|
||||
<th>query</th>
|
||||
<th>time</th>
|
||||
</tr>
|
||||
<?php
|
||||
$totalTime = 0;
|
||||
$curElem = 0;
|
||||
$maxLen = 125;
|
||||
for ($i = 0, $j = count(self::$dbLog); $i < $j; $i+=2)
|
||||
{
|
||||
$log1 = self::$dbLog[$i];
|
||||
$log2 = isset(self::$dbLog[$i+1]) ? self::$dbLog[$i+1] : array();
|
||||
$isError = empty($log2['error']) ? false : true;
|
||||
|
||||
$execTime = $log2['time'] - $log1['time'];
|
||||
$totalTime += $execTime;
|
||||
$color = $isError ? 'red' : 'green';
|
||||
$error = $log2['error'];
|
||||
$query = $log1['q'];
|
||||
$time = number_format($execTime , 5, '.', ' ');
|
||||
$count = $log1['count'];
|
||||
?>
|
||||
<tr>
|
||||
<td class="num"><?= (int)(++$curElem)?></td>
|
||||
<td style='color: <?= $color?>'>
|
||||
<pre><?= wordwrap($query, $maxLen, PHP_EOL)?></pre>
|
||||
<?php if ($isError):?>
|
||||
<div id='iDataBaseLog_<?= $i?>'><pre><?= wordwrap(print_r($error, true), $maxLen, PHP_EOL)?></pre></div>
|
||||
<?php endif?>
|
||||
</td>
|
||||
<td><?= $time?></td>
|
||||
</tr>
|
||||
<?php
|
||||
if ($isError)
|
||||
{
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<tr class="total">
|
||||
<td colspan='2' class="center"><?= $curElem?> <span>queries</span></td>
|
||||
<td><?= number_format($totalTime, 5, '.', ' ')?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
};
|
||||
?>
|
||||
99
lib/Debug/ErrorHook/Catcher.php
Executable file
99
lib/Debug/ErrorHook/Catcher.php
Executable file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* Auxilary class.
|
||||
* It performs all work with notification catching.
|
||||
*/
|
||||
class Debug_ErrorHook_Catcher
|
||||
{
|
||||
private $_notifiers = array();
|
||||
private $_active = true;
|
||||
private $_prevHdl = null;
|
||||
private $_types = array(
|
||||
"E_ERROR", "E_WARNING", "E_PARSE", "E_NOTICE", "E_CORE_ERROR",
|
||||
"E_CORE_WARNING", "E_COMPILE_ERROR", "E_COMPILE_WARNING",
|
||||
"E_USER_ERROR", "E_USER_WARNING", "E_USER_NOTICE", "E_STRICT",
|
||||
"E_RECOVERABLE_ERROR", "E_DEPRECATED", "E_USER_DEPRECATED",
|
||||
);
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->_prevHdl = set_error_handler(array($this, "_handleNotice"));
|
||||
register_shutdown_function(array($this, "_handleFatal"));
|
||||
}
|
||||
|
||||
public function remove()
|
||||
{
|
||||
restore_error_handler();
|
||||
$this->_prevHdl = null;
|
||||
// There is no unregister_shutdown_function(), so we emulate it via flag.
|
||||
$this->_active = false;
|
||||
}
|
||||
|
||||
public function addNotifier(Debug_ErrorHook_INotifier $notifier)
|
||||
{
|
||||
$this->_notifiers[] = $notifier;
|
||||
}
|
||||
|
||||
public function _handleNotice($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
if (!($errno & error_reporting())) {
|
||||
return $this->_callPrevHdl($errno, $errstr, $errfile, $errline);
|
||||
}
|
||||
$trace = debug_backtrace();
|
||||
array_shift($trace);
|
||||
if ($this->_notify($errno, $errstr, $errfile, $errline, $trace) === false) {
|
||||
return $this->_callPrevHdl($errno, $errstr, $errfile, $errline, $trace);
|
||||
}
|
||||
}
|
||||
|
||||
public function _handleFatal()
|
||||
{
|
||||
$error = error_get_last();
|
||||
if (!$this->_active || !is_array($error) || !in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR))) {
|
||||
return;
|
||||
}
|
||||
$this->_notify($error['type'], $error['message'], $error['file'], $error['line'], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a notification.
|
||||
*
|
||||
* @param mixed $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
* @param array $trace
|
||||
* @return bool True if we need to stop the processing.
|
||||
*/
|
||||
private function _notify($errno, $errstr, $errfile, $errline, $trace)
|
||||
{
|
||||
// Translate error number to error name.
|
||||
if (is_numeric($errno)) {
|
||||
foreach ($this->_types as $t) {
|
||||
if (defined($t)) {
|
||||
$e = constant($t);
|
||||
if ($errno == $e) {
|
||||
$errno = $t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Send data to all notifiers.
|
||||
foreach ($this->_notifiers as $notifier) {
|
||||
if ($notifier->notify($errno, $errstr, $errfile, $errline, $trace) === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function _callPrevHdl()
|
||||
{
|
||||
if ($this->_prevHdl) {
|
||||
$args = func_get_args();
|
||||
return call_user_func_array($this->_prevHdl, $args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
19
lib/Debug/ErrorHook/INotifier.php
Executable file
19
lib/Debug/ErrorHook/INotifier.php
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* Notifier interface.
|
||||
* Should be implemented by all notifiers in the stack.
|
||||
*/
|
||||
interface Debug_ErrorHook_INotifier
|
||||
{
|
||||
/**
|
||||
* Called when an error occurred.
|
||||
*
|
||||
* @param string $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param string $errline
|
||||
* @param array $trace
|
||||
* @return void
|
||||
*/
|
||||
public function notify($errno, $errstr, $errfile, $errline, $trace);
|
||||
}
|
||||
47
lib/Debug/ErrorHook/Listener.php
Executable file
47
lib/Debug/ErrorHook/Listener.php
Executable file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Class to catch notices, warnings and even fatal errors
|
||||
* and push them to a number of notifiers (e.g. - to email).
|
||||
*
|
||||
* A Listener object is kind of a guard object.
|
||||
*
|
||||
* @version 1.00
|
||||
*/
|
||||
|
||||
class Debug_ErrorHook_Listener
|
||||
{
|
||||
private $_catcher = null;
|
||||
|
||||
/**
|
||||
* Creates a new listener object.
|
||||
* When this object is destroyed, all hooks are removed.
|
||||
*
|
||||
* @return Debug_ErrorHook_Listener
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_catcher = new Debug_ErrorHook_Catcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor. Cancels all listenings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->_catcher->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new notifier to the list. Notifiers are called in case
|
||||
* of notices and even fatal errors.
|
||||
*
|
||||
* @param Debug_ErrorHook_INotifier $notifier
|
||||
* @return void
|
||||
*/
|
||||
public function addNotifier(Debug_ErrorHook_INotifier $notifier)
|
||||
{
|
||||
$this->_catcher->addNotifier($notifier);
|
||||
}
|
||||
}
|
||||
57
lib/Debug/ErrorHook/MailNotifier.php
Executable file
57
lib/Debug/ErrorHook/MailNotifier.php
Executable file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Sends all notifications to a specified email.
|
||||
*
|
||||
* Consider using this class together with Debug_ErrorHook_RemoveDupsWrapper
|
||||
* to avoid mail server flooding when a lot of errors arrives.
|
||||
*/
|
||||
|
||||
class Debug_ErrorHook_MailNotifier extends Debug_ErrorHook_TextNotifier
|
||||
{
|
||||
private $_to;
|
||||
private $_charset;
|
||||
private $_whatToSend;
|
||||
private $_subjPrefix;
|
||||
|
||||
public function __construct($to, $whatToSend, $subjPrefix = "[ERROR] ", $charset = "UTF-8")
|
||||
{
|
||||
parent::__construct($whatToSend);
|
||||
$this->_to = $to;
|
||||
$this->_subjPrefix = $subjPrefix;
|
||||
$this->_charset = $charset;
|
||||
}
|
||||
|
||||
protected function _notifyText($subject, $body)
|
||||
{
|
||||
$this->_mail(
|
||||
$this->_to,
|
||||
$this->_encodeMailHeader($this->_subjPrefix . $subject),
|
||||
$body,
|
||||
join("\r\n", array(
|
||||
"From: {$this->_to}",
|
||||
"Content-Type: text/plain; charset={$this->_charset}"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
protected function _mail()
|
||||
{
|
||||
$args = func_get_args();
|
||||
@call_user_func_array("mail", $args);
|
||||
}
|
||||
|
||||
private function _encodeMailHeader($header)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/((?:^|>)\s*)([^<>]*?[^\w\s.][^<>]*?)(\s*(?:<|$))/s',
|
||||
array(__CLASS__, '_encodeMailHeaderCallback'),
|
||||
$header
|
||||
);
|
||||
}
|
||||
|
||||
private function _encodeMailHeaderCallback($p)
|
||||
{
|
||||
$encoding = $this->_charset;
|
||||
return $p[1] . "=?$encoding?B?" . base64_encode($p[2]) . "?=" . $p[3];
|
||||
}
|
||||
}
|
||||
87
lib/Debug/ErrorHook/RemoveDupsWrapper.php
Executable file
87
lib/Debug/ErrorHook/RemoveDupsWrapper.php
Executable file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Wrapper which denies duplicated notifications to be
|
||||
* processed again and again. It is needed to lower the
|
||||
* traffic to mail server in case the site is down.
|
||||
*
|
||||
* This class stores meta-informations in filesystem.
|
||||
* It takes care about garbage collecting.
|
||||
*/
|
||||
|
||||
class Debug_ErrorHook_RemoveDupsWrapper implements Debug_ErrorHook_INotifier
|
||||
{
|
||||
const DEFAULT_PERIOD = 300;
|
||||
const ERROR_FILE_SUFFIX = ".error";
|
||||
const GC_PROBABILITY = 0.01;
|
||||
|
||||
private $_notifier;
|
||||
private $_tmpPath;
|
||||
private $_period;
|
||||
private $_gcExecuted = false;
|
||||
|
||||
public function __construct(Debug_ErrorHook_INotifier $notifier, $tmpPath = null, $period = null)
|
||||
{
|
||||
$this->_tmpPath = $tmpPath? $tmpPath : $this->_getDefaultTmpPath();
|
||||
$this->_period = $period? $period : self::DEFAULT_PERIOD;
|
||||
$this->_notifier = $notifier;
|
||||
if (!@is_dir($this->_tmpPath)) {
|
||||
if (!@mkdir($this->_tmpPath, 0777, true)) {
|
||||
$error = error_get_last();
|
||||
throw new Exception("Cannot create '{$this->_tmpPath}': {$error['message']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function notify($errno, $errstr, $errfile, $errline, $trace)
|
||||
{
|
||||
$hash = md5(join(":", array($errno, $errfile, $errline)));
|
||||
if ($this->_isExpired($hash)) {
|
||||
$this->_notifier->notify($errno, $errstr, $errfile, $errline, $trace);
|
||||
}
|
||||
// Touch always, even if we did not send anything. Else same errors will
|
||||
// be mailed again and again after $period (e.g. once per 5 minutes).
|
||||
$this->_touch($hash, $errfile, $errline);
|
||||
}
|
||||
|
||||
protected function _getDefaultTmpPath()
|
||||
{
|
||||
return sys_get_temp_dir() . "/" . get_class($this);
|
||||
}
|
||||
|
||||
protected function _getGcProbability()
|
||||
{
|
||||
return self::GC_PROBABILITY;
|
||||
}
|
||||
|
||||
private function _getLockFname($hash)
|
||||
{
|
||||
return $this->_tmpPath . '/' . $hash . self::ERROR_FILE_SUFFIX;
|
||||
}
|
||||
|
||||
private function _isExpired($hash)
|
||||
{
|
||||
$file = $this->_getLockFname($hash);
|
||||
return !file_exists($file) || filemtime($file) < time() - $this->_period;
|
||||
}
|
||||
|
||||
private function _touch($hash, $errfile, $errline)
|
||||
{
|
||||
$file = $this->_getLockFname($hash);
|
||||
file_put_contents($file, "$errfile:$errline");
|
||||
@chmod($file, 0666);
|
||||
$this->_gc();
|
||||
}
|
||||
|
||||
private function _gc()
|
||||
{
|
||||
if ($this->_gcExecuted || mt_rand(0, 10000) >= $this->_getGcProbability() * 10000) {
|
||||
return;
|
||||
}
|
||||
foreach (glob("{$this->_tmpPath}/*" . self::ERROR_FILE_SUFFIX) as $file) {
|
||||
if (filemtime($file) <= time() - $this->_period * 2) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
$this->_gcExecuted = true;
|
||||
}
|
||||
}
|
||||
78
lib/Debug/ErrorHook/TextNotifier.php
Executable file
78
lib/Debug/ErrorHook/TextNotifier.php
Executable file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Generic notifier wrapper. Converts notification
|
||||
* to a human-readable text representation.
|
||||
*/
|
||||
|
||||
abstract class Debug_ErrorHook_TextNotifier implements Debug_ErrorHook_INotifier
|
||||
{
|
||||
const LOG_SERVER = 1;
|
||||
const LOG_TRACE = 2;
|
||||
const LOG_COOKIE = 4;
|
||||
const LOG_GET = 8;
|
||||
const LOG_POST = 16;
|
||||
const LOG_SESSION = 32;
|
||||
const LOG_ALL = 65535;
|
||||
|
||||
private $_whatToLog;
|
||||
private $_bodySuffix;
|
||||
|
||||
public function __construct($whatToLog)
|
||||
{
|
||||
$this->_whatToLog = $whatToLog;
|
||||
}
|
||||
|
||||
public function setBodySuffixTest($text)
|
||||
{
|
||||
$this->_bodySuffix = $text;
|
||||
}
|
||||
|
||||
public function notify($errno, $errstr, $errfile, $errline, $trace)
|
||||
{
|
||||
$body = array();
|
||||
$body[] = $this->_makeSection(
|
||||
"",
|
||||
join("\n", array(
|
||||
(@$_SERVER['GATEWAY_INTERFACE']? "//{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}" : ""),
|
||||
"$errno: $errstr",
|
||||
"at $errfile on line $errline",
|
||||
))
|
||||
);
|
||||
if ($this->_whatToLog & self::LOG_TRACE && $trace) {
|
||||
$body[] = $this->_makeSection("TRACE", Debug_ErrorHook_Util::backtraceToString($trace));
|
||||
}
|
||||
if ($this->_whatToLog & self::LOG_SERVER) {
|
||||
$body[] = $this->_makeSection("SERVER", Debug_ErrorHook_Util::varExport($_SERVER));
|
||||
}
|
||||
if ($this->_whatToLog & self::LOG_COOKIE) {
|
||||
$body[] = $this->_makeSection("COOKIES", Debug_ErrorHook_Util::varExport($_COOKIE));
|
||||
}
|
||||
if ($this->_whatToLog & self::LOG_GET) {
|
||||
$body[] = $this->_makeSection("GET", Debug_ErrorHook_Util::varExport($_GET));
|
||||
}
|
||||
if ($this->_whatToLog & self::LOG_POST) {
|
||||
$body[] = $this->_makeSection("POST", Debug_ErrorHook_Util::varExport($_POST));
|
||||
}
|
||||
if ($this->_whatToLog & self::LOG_SESSION) {
|
||||
$body[] = $this->_makeSection("SESSION", Debug_ErrorHook_Util::varExport(@$_SESSION));
|
||||
}
|
||||
// Append body suffix?
|
||||
$suffix = $this->_bodySuffix && is_callable($this->_bodySuffix)? call_user_func($this->_bodySuffix) : $this->_bodySuffix;
|
||||
if ($suffix) {
|
||||
$body[] = $this->_makeSection("ADDITIONAL INFO", $suffix);
|
||||
}
|
||||
// Remain only 1st line for subject.
|
||||
$errstr = preg_replace("/\r?\n.*/s", '', $errstr);
|
||||
$this->_notifyText("$errno: $errstr at $errfile on line $errline", join("\n", $body));
|
||||
}
|
||||
|
||||
private function _makeSection($name, $body)
|
||||
{
|
||||
$body = rtrim($body);
|
||||
if ($name) $body = preg_replace('/^/m', ' ', $body);
|
||||
$body = preg_replace('/^([ \t\r]*\n)+/s', '', $body);
|
||||
return ($name? $name . ":\n" : "") . $body . "\n";
|
||||
}
|
||||
|
||||
abstract protected function _notifyText($subject, $body);
|
||||
}
|
||||
102
lib/Debug/ErrorHook/Util.php
Executable file
102
lib/Debug/ErrorHook/Util.php
Executable file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
class Debug_ErrorHook_Util
|
||||
{
|
||||
/**
|
||||
* var_export clone, without using output buffering.
|
||||
* (For calls in ob_handler)
|
||||
*
|
||||
* @param mixed $var to be exported
|
||||
* @param integer $maxLevel (recursion protect)
|
||||
* @param integer $level of current indent
|
||||
* @return string
|
||||
*/
|
||||
public static function varExport($var, $maxLevel = 10, $level = 0)
|
||||
{
|
||||
$escapes = "\"\r\t\x00\$";
|
||||
$tab = ' ';
|
||||
|
||||
if (is_bool($var)) {
|
||||
return $var ? 'TRUE' : 'FALSE';
|
||||
} elseif (is_string($var)) {
|
||||
return '"' . addcslashes($var, $escapes) . '"';
|
||||
} elseif (is_float($var) || is_int($var)) {
|
||||
return $var;
|
||||
} elseif (is_null($var)) {
|
||||
return 'NULL';
|
||||
} elseif (is_resource($var)) {
|
||||
return 'NULL /* ' . $var . ' */';
|
||||
}
|
||||
|
||||
if ($maxLevel < $level) {
|
||||
return 'NULL /* ' . (string) $var . ' MAX LEVEL ' . $maxLevel . " REACHED*/";
|
||||
}
|
||||
|
||||
if (is_array($var)) {
|
||||
$return = "array(\n";
|
||||
} else {
|
||||
$return = get_class($var) . "::__set_state(array(\n";
|
||||
}
|
||||
|
||||
$offset = str_repeat($tab, $level + 1);
|
||||
|
||||
foreach ((array) $var as $key => $value) {
|
||||
$return .= $offset;
|
||||
if (is_int($key)) {
|
||||
$return .= $key;
|
||||
} else {
|
||||
$return .= '"' . addcslashes($key, $escapes). '"';
|
||||
}
|
||||
$return .= ' => ' . self::varExport($value, $maxLevel, $level + 1) . ",\n";
|
||||
}
|
||||
|
||||
return $return
|
||||
. str_repeat($tab, $level)
|
||||
. (is_array($var) ? ')' : '))');
|
||||
}
|
||||
|
||||
/**
|
||||
* Analog for debug_print_backtrace(), but returns string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function backtraceToString($backtrace)
|
||||
{
|
||||
// Iterate backtrace
|
||||
$calls = array();
|
||||
foreach ($backtrace as $i => $call) {
|
||||
if (!isset($call['file'])) {
|
||||
$call['file'] = '(null)';
|
||||
}
|
||||
if (!isset($call['line'])) {
|
||||
$call['line'] = '0';
|
||||
}
|
||||
$location = $call['file'] . ':' . $call['line'];
|
||||
$function = (isset($call['class'])) ?
|
||||
$call['class'] . (isset($call['type']) ? $call['type'] : '.') . $call['function'] :
|
||||
$call['function'];
|
||||
|
||||
$params = '';
|
||||
if (isset($call['args']) && is_array($call['args'])) {
|
||||
$args = array();
|
||||
foreach ($call['args'] as $arg) {
|
||||
if (is_array($arg)) {
|
||||
$args[] = "Array(...)";
|
||||
} elseif (is_object($arg)) {
|
||||
$args[] = get_class($arg);
|
||||
} else {
|
||||
$args[] = $arg;
|
||||
}
|
||||
}
|
||||
$params = implode(', ', $args);
|
||||
}
|
||||
|
||||
$calls[] = sprintf('#%d %s(%s) called at [%s]',
|
||||
$i,
|
||||
$function,
|
||||
$params,
|
||||
$location);
|
||||
}
|
||||
|
||||
return implode("\n", $calls) . "\n";
|
||||
}
|
||||
}
|
||||
37
lib/Debug/ErrorHook/my/MyDebug_ErrorHook_TextNotifier.php
Executable file
37
lib/Debug/ErrorHook/my/MyDebug_ErrorHook_TextNotifier.php
Executable file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Вывод ошибки на экран и лог или только в лог в зависимоти от DEBUG_MODE
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class MyDebug_ErrorHook_TextNotifier extends Debug_ErrorHook_TextNotifier
|
||||
{
|
||||
protected function _notifyText($subject, $body)
|
||||
{
|
||||
global $g_config;
|
||||
|
||||
// Подготовка сообщения ошибки
|
||||
$msg = PHP_EOL .
|
||||
"Text notification:" . PHP_EOL .
|
||||
"\tsubject: {$subject}" . PHP_EOL .
|
||||
"\t{$body}" .
|
||||
PHP_EOL;
|
||||
|
||||
// Запись ошибки в лог-файл
|
||||
$path = $g_config['logErrors']['logFile'];
|
||||
$fileLogger = FileLogger::Create($path);
|
||||
$fileLogger->Error($msg);
|
||||
|
||||
// Вывод ошибки на экран
|
||||
if ($g_config['phpIni']['display_errors'])
|
||||
{
|
||||
echo "<pre>$msg</pre>";
|
||||
}
|
||||
else
|
||||
{
|
||||
IncludeCom('500');
|
||||
}
|
||||
}
|
||||
};
|
||||
?>
|
||||
87
lib/DebugPanel.php
Executable file
87
lib/DebugPanel.php
Executable file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Информационная панель
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class DebugPanel
|
||||
{
|
||||
const PHP_INI_USER = 1;
|
||||
const PHP_INI_PERDIR = 2;
|
||||
const PHP_INI_SYSTEM = 4;
|
||||
const PHP_INI_ALL = 7;
|
||||
|
||||
public function Files()
|
||||
{
|
||||
$files = get_included_files();
|
||||
$stat = array();
|
||||
foreach ($files as $file)
|
||||
{
|
||||
$stat[] = array('file' => $file,
|
||||
'size' => FileSys::Size($file),
|
||||
'lines' => count(file($file)));
|
||||
}
|
||||
return $stat;
|
||||
}
|
||||
|
||||
public function TotalFileSize()
|
||||
{
|
||||
$total = 0;
|
||||
foreach (get_included_files() as $f)
|
||||
{
|
||||
$total += filesize($f);
|
||||
}
|
||||
$size = sprintf("%u", $total);
|
||||
$filesizename = array(" Bytes", " Kb", " Mb", " Gb", " Tb", " Pb", " Eb", " Zb", " Yb");
|
||||
return $size ? round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . $filesizename[$i] : '0 Bytes';
|
||||
}
|
||||
|
||||
public function TotalFileLines()
|
||||
{
|
||||
$total = 0;
|
||||
foreach (get_included_files() as $f)
|
||||
{
|
||||
$total += count(file($f));
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
public function Db()
|
||||
{
|
||||
return class_exists('MyDataBaseLog') ? MyDataBaseLog::Render() : '';
|
||||
}
|
||||
|
||||
public function MemoryUsage()
|
||||
{
|
||||
$size = sprintf("%u", memory_get_usage());
|
||||
$filesizename = array(" Bytes", " Kb", " Mb", " Gb", " Tb", " Pb", " Eb", " Zb", " Yb");
|
||||
return $size ? round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . $filesizename[$i] : '0 Bytes';
|
||||
}
|
||||
|
||||
public static function ShowPhpIniAccess($access)
|
||||
{
|
||||
switch ($access)
|
||||
{
|
||||
case self::PHP_INI_USER:
|
||||
$ret = 'scripts';
|
||||
break;
|
||||
case self::PHP_INI_PERDIR:
|
||||
$ret = 'php.ini | .htaccess | httpd.conf';
|
||||
break;
|
||||
case 6:
|
||||
$ret = 'php.ini | .htaccess | httpd.conf';
|
||||
break;
|
||||
case self::PHP_INI_SYSTEM:
|
||||
$ret = 'php.ini | httpd.conf';
|
||||
break;
|
||||
case self::PHP_INI_ALL:
|
||||
$ret = 'anywhere';
|
||||
break;
|
||||
default:
|
||||
$ret = '-';
|
||||
};
|
||||
return $ret;
|
||||
}
|
||||
};
|
||||
?>
|
||||
157
lib/ExtraPacker/Config.php
Executable file
157
lib/ExtraPacker/Config.php
Executable file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс настроечных функций ExtraPacker-а
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class ExtraPacker_Config
|
||||
{
|
||||
private static function GetDocRoot()
|
||||
{
|
||||
$path = BASEPATH;
|
||||
if (SITE_IN_DIR)
|
||||
{
|
||||
$path = substr($path, 0, strrpos($path, SITE_IN_DIR));
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
private static function GetPathFromUrl($url)
|
||||
{
|
||||
$url = substr($url, strpos($url, SITE_ROOT) + strlen(SITE_ROOT));
|
||||
return self::GetDocRoot() . $url;
|
||||
}
|
||||
|
||||
public static function GetPathJsFileFromUrl($url)
|
||||
{
|
||||
return self::GetPathFromUrl($url);
|
||||
}
|
||||
|
||||
public static function GetPathCssFileFromUrl($url)
|
||||
{
|
||||
return self::GetPathFromUrl($url);
|
||||
}
|
||||
|
||||
private static function GetAddrPackFile($path)
|
||||
{
|
||||
return str_replace(self::GetDocRoot(), '/', $path);
|
||||
}
|
||||
|
||||
public static function GetAddrJsPackFile($path)
|
||||
{
|
||||
return self::GetAddrPackFile($path);
|
||||
}
|
||||
|
||||
public static function GetAddrCssPackFile($path)
|
||||
{
|
||||
return self::GetAddrPackFile($path);
|
||||
}
|
||||
|
||||
public static function ChangeCssUrl($m)
|
||||
{
|
||||
// Если сайт в папке то надо добавить эту папку в пути поиска
|
||||
$docRoot = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']);
|
||||
$docRoot = substr($docRoot, -1, 1) == '/' ? substr($docRoot, 0, -1) : $docRoot;
|
||||
|
||||
$dir = str_replace($docRoot, '', BASEPATH);
|
||||
$dir = trim($dir, '/');
|
||||
$dir = empty($dir) ? '' : "/{$dir}";
|
||||
|
||||
$curUrl = $dir . $GLOBALS['__engineExtraPackerChangeCssUrl__curUrl__'];
|
||||
$url = isset($m[1]) ? trim($m[1], '\'"') : '';
|
||||
if ($url)
|
||||
{
|
||||
// Это не Less-переменная, не путь вида http://google.com/ и не url(data:image/png;base64,....
|
||||
if (strpos($url, "@") === false && strpos($url, "://") === false && strpos($url, "data:") !== 0)
|
||||
{
|
||||
// Это не абсолютный адрес
|
||||
if ($url[0] !== '/')
|
||||
{
|
||||
$url = "$curUrl/$url";
|
||||
}
|
||||
// Если это абсолтный путь но к корню это сайта, но сайт запущен из каталога то переделываем пути так что бы они были как-будто из каталога
|
||||
elseif (strlen($dir) && strpos($url, $dir) === false)
|
||||
{
|
||||
$url = $dir . $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function ChangeCssUrl_Url($url)
|
||||
{
|
||||
$url = self::ChangeCssUrl($url);
|
||||
return "url('$url')";
|
||||
}
|
||||
|
||||
public static function ChangeCssUrl_Src($url)
|
||||
{
|
||||
$url = self::ChangeCssUrl($url);
|
||||
return "src='$url'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Предобработка каждого файла перед помещением в общий архив
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $file
|
||||
* @param string $type либо js либо css
|
||||
*/
|
||||
public static function PrepareEachFile($content, $path, $type)
|
||||
{
|
||||
global $g_config;
|
||||
$url = str_replace(BASEPATH, '/', $path);
|
||||
if ($type == 'css')
|
||||
{
|
||||
// Меняем пути к файлам
|
||||
$GLOBALS['__engineExtraPackerChangeCssUrl__curUrl__'] = dirname($url);
|
||||
$content = preg_replace_callback("~url\((.*?)\)~is", array(__CLASS__, "ChangeCssUrl_Url"), $content);
|
||||
unset($GLOBALS['__engineExtraPackerChangeCssUrl__curUrl__']);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготовка спакованного js-контента перед записью в файл
|
||||
*
|
||||
* Данная функция по умолчанию дописывает пустой обработчик ошибок в начало js файла если это production
|
||||
*/
|
||||
public static function PrepareAllJs($content)
|
||||
{
|
||||
global $g_config;
|
||||
|
||||
$noErrorsCode = '';
|
||||
|
||||
// Если не debug режим то выключаем javascript ошибки
|
||||
if (!$g_config['phpIni']['display_errors'])
|
||||
{
|
||||
ob_start();
|
||||
?>
|
||||
function __MyErrHandler(msg)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
window.onerror = __MyErrHandler;
|
||||
<?php
|
||||
$noErrorsCode = ob_get_clean();
|
||||
}
|
||||
return $noErrorsCode . $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготовка всего спакованного контента css перед записью в файл
|
||||
*
|
||||
* Пример использования:
|
||||
* Пусть у нас галит ф-я переписывания путей к картинкам в css, и она ставит BASEPATH тогда можно сделать что-то вроде такого:
|
||||
* return str_replace("/home/Sites/test.com/i/", "/i/")
|
||||
*/
|
||||
public static function PrepareAllCss($content)
|
||||
{
|
||||
return $content;
|
||||
}
|
||||
};
|
||||
?>
|
||||
692
lib/ExtraPacker/ExtraPacker.php
Executable file
692
lib/ExtraPacker/ExtraPacker.php
Executable file
@@ -0,0 +1,692 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__FILE__) . "/Lib/CssPacker.php";
|
||||
require_once dirname(__FILE__) . "/Lib/HtmlPacker.php";
|
||||
require_once dirname(__FILE__) . "/Lib/JSMin.php";
|
||||
require_once dirname(__FILE__) . "/Lib/lessc.inc.php";
|
||||
|
||||
|
||||
/**
|
||||
* ExtraPacker - для склеивания и сжатия css и js файлов, а так же сжатия html-контента
|
||||
*
|
||||
* Выбирает все подключаемые js-ки (тоже с css-ками) собирает в 1 файл и пакует записывая файл info с информацией по запакованным файлам.
|
||||
* Поддерживает механизм транзакций.
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class ExtraPacker
|
||||
{
|
||||
/**
|
||||
* Коэфициент сжатия для js/css при GZIP сжатии
|
||||
*/
|
||||
const COEF_GZIP_COMPRESS = 9;
|
||||
|
||||
/**
|
||||
* Тег после которого должны встать ссылки на запакованные js и css файлы должен рассполагаться в head разделе документа
|
||||
*/
|
||||
const TAG_EXTRAPACKER = '<!-- extraPacker -->';
|
||||
|
||||
const MEMORY_FOR_PACKING = 67108864; // 64Mb
|
||||
|
||||
private $addrJsCacheFileInfo;
|
||||
private $addrJsCacheFile;
|
||||
private $addrCssCacheFileInfo;
|
||||
private $addrCssCacheFile;
|
||||
private $addrNumJsTrans;
|
||||
private $addrNumCssTrans;
|
||||
private $flgHtmlPack;
|
||||
private $flgCssPack;
|
||||
private $flgJsPack;
|
||||
private $flgUseTransSystem;
|
||||
private $gzipPostfix = '.gz';
|
||||
private $pCallBackFunctionJS = NULL;
|
||||
private $pCallBackFunctionCSS = NULL;
|
||||
private $pFuncGetAddrJsPackFile = NULL;
|
||||
private $pFuncGetAddrCssPackFile = NULL;
|
||||
private $pGetPathJsFileFromUrl = NULL;
|
||||
private $pGetPathCssFileFromUrl = NULL;
|
||||
private $arrExeptions_js;
|
||||
private $arrExeptionsNotAdd_js;
|
||||
private $arrExeptions_css;
|
||||
private $arrExeptionsNotAdd_css;
|
||||
private $pFuncPreWriteCache;
|
||||
private $pFuncPostWriteCache;
|
||||
private $flgUseGZIP;
|
||||
private $flgBuffering;
|
||||
private $pPrepareEachFile = NULL;
|
||||
|
||||
// Только склеивать файлы
|
||||
const ONLY_MERGE_CSS = false;
|
||||
const ONLY_MERGE_JS = false;
|
||||
|
||||
// Перепаковывать каждый раз (удобно если = DEBUG_MODE)
|
||||
const ALWAYS_INDEPENDENT_PACK = false;
|
||||
const ALWAYS_REPACK = false;
|
||||
|
||||
private static function MakeDir($path)
|
||||
{
|
||||
if (is_dir($path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$path = explode("/", $path);
|
||||
if (count($path) == 1)
|
||||
{
|
||||
$path = explode("\\", $path[0]);
|
||||
}
|
||||
|
||||
$d = "";
|
||||
foreach ($path as $dir)
|
||||
{
|
||||
$d .= $dir . "/";
|
||||
if ($d != "/" && $d != "/home/" && !is_dir($d))
|
||||
{
|
||||
@mkdir($d, 0777);
|
||||
@chmod($d, 0777);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function WriteFile($file, $b)
|
||||
{
|
||||
self::MakeDir(dirname($file));
|
||||
fclose(fopen($file, "a+"));
|
||||
$f = fopen($file,"r+");
|
||||
fwrite($f, $b);
|
||||
fclose($f);
|
||||
@chmod($file, 0777);
|
||||
}
|
||||
|
||||
private function HtmlPack($html)
|
||||
{
|
||||
return Minify_HTML::minify($html);
|
||||
}
|
||||
|
||||
private function PrepareJsContent($jsContent)
|
||||
{
|
||||
if ($this->pCallBackFunctionJS)
|
||||
{
|
||||
$jsContent = call_user_func($this->pCallBackFunctionJS, $jsContent);
|
||||
}
|
||||
|
||||
return $jsContent;
|
||||
}
|
||||
|
||||
private function PrepareCssContent($cssContent)
|
||||
{
|
||||
if ($this->pCallBackFunctionCSS)
|
||||
{
|
||||
$cssContent = call_user_func($this->pCallBackFunctionCSS, $cssContent);
|
||||
}
|
||||
|
||||
return $cssContent;
|
||||
}
|
||||
|
||||
private static function ReadFile($file, $flag = false)
|
||||
{
|
||||
$file = trim($file);
|
||||
if (!strlen(trim($file)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($flag && $file[0] == "/")
|
||||
{
|
||||
$file = $_SERVER["DOCUMENT_ROOT"] . $file;
|
||||
}
|
||||
|
||||
$b = false;
|
||||
if (strstr($file, "http://") == $file)
|
||||
{
|
||||
$f = fopen($file, "r");
|
||||
while (!feof($f))
|
||||
{
|
||||
$b .= fread($f, 1024);
|
||||
}
|
||||
}
|
||||
elseif (file_exists($file) && is_readable($file))
|
||||
{
|
||||
$f = fopen($file, "r");
|
||||
$b = fread($f, filesize($file));
|
||||
}
|
||||
|
||||
if (isset($f) && $f)
|
||||
{
|
||||
fclose($f);
|
||||
}
|
||||
|
||||
return $b;
|
||||
}
|
||||
|
||||
private function GetTrans($type)
|
||||
{
|
||||
if (file_exists($type == 'js' ? $this->addrNumJsTrans : $this->addrNumCssTrans))
|
||||
{
|
||||
$number = self::ReadFile($type == 'js' ? $this->addrNumJsTrans : $this->addrNumCssTrans);
|
||||
return (int)$number;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function IncTrans($type)
|
||||
{
|
||||
self::WriteFile($type == 'js' ? $this->addrNumJsTrans : $this->addrNumCssTrans, time());
|
||||
}
|
||||
|
||||
private function UnickList($arrFiles)
|
||||
{
|
||||
$arrRes = array();
|
||||
foreach ($arrFiles as $file)
|
||||
{
|
||||
$arrRes[$file['addr']] = $file;
|
||||
}
|
||||
|
||||
return array_values($arrRes);
|
||||
}
|
||||
|
||||
public static function MemInBytes($mem)
|
||||
{
|
||||
$lastSymbol = strtoupper(substr($mem, -1));
|
||||
$mem = in_array($lastSymbol, array('K', 'M', 'G')) ? substr($mem, 0, -1) : $mem;
|
||||
switch ($lastSymbol)
|
||||
{
|
||||
case 'K':
|
||||
$mem *= 1024;
|
||||
break;
|
||||
case 'M':
|
||||
$mem *= (1024 * 1024);
|
||||
break;
|
||||
case 'G':
|
||||
$mem *= (1024 * 1024 * 1024);
|
||||
break;
|
||||
};
|
||||
return $mem;
|
||||
}
|
||||
|
||||
private function WriteDataInCache($type, $arrFiles, $arrExeptions = array())
|
||||
{
|
||||
// Устанавливаем памяти на выполнение побольше т.к. будет идти создание pack-версии js/css
|
||||
global $g_config;
|
||||
$memInBytes = self::MemInBytes($g_config['phpIni']['memory_limit']);
|
||||
if (self::MEMORY_FOR_PACKING > $memInBytes)
|
||||
{
|
||||
ini_set('memory_limit', self::MEMORY_FOR_PACKING);
|
||||
}
|
||||
// Устанавлиаем время выполнения скрипта больше той что установлено сейчас на 30 секунд
|
||||
ini_set('max_execution_time', intval(intval($g_config['phpIni']['max_execution_time']) + 30));
|
||||
|
||||
|
||||
if ($this->pFuncPreWriteCache)
|
||||
{
|
||||
call_user_func($this->pFuncPreWriteCache);
|
||||
}
|
||||
|
||||
if ($this->flgUseTransSystem)
|
||||
{
|
||||
$this->IncTrans($type);
|
||||
}
|
||||
|
||||
if ($this->flgUseTransSystem)
|
||||
{
|
||||
$ext = '.' . $type;
|
||||
$t = str_replace($ext, self::GetTrans($type) . $ext, $type == 'js' ? $this->addrJsCacheFile : $this->addrCssCacheFile);
|
||||
$type == 'js' ? $this->addrJsCacheFile = $t : $this->addrCssCacheFile = $t;
|
||||
}
|
||||
|
||||
// Удаляем файлы из списка для запаковки которых уже нет на сервере
|
||||
foreach ($arrFiles as $k => $v)
|
||||
{
|
||||
if (!file_exists($v['addr']))
|
||||
{
|
||||
unset($arrFiles[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$packInfoAboutFiles = serialize($arrFiles);
|
||||
self::WriteFile($type == 'js' ? $this->addrJsCacheFileInfo : $this->addrCssCacheFileInfo, $packInfoAboutFiles);
|
||||
|
||||
|
||||
foreach ($arrExeptions as $k => $v)
|
||||
{
|
||||
$arrExeptions[$k] = call_user_func($type == 'js' ? $this->pGetPathJsFileFromUrl : $this->pGetPathCssFileFromUrl , $v);
|
||||
}
|
||||
|
||||
$endl = $type == 'js' ? ";" : '';
|
||||
$fullExeptionContent = "";
|
||||
$fullContent = "";
|
||||
foreach ($arrFiles as $file)
|
||||
{
|
||||
$file = $file['addr'];
|
||||
$curContent = file_get_contents($file);
|
||||
// Если стоит обработка каждого файла перед добавлением в сжатый архив
|
||||
if ($this->pPrepareEachFile)
|
||||
{
|
||||
$curContent = call_user_func($this->pPrepareEachFile, $curContent, $file, $type);
|
||||
}
|
||||
if (in_array($file, $arrExeptions))
|
||||
{
|
||||
$fullExeptionContent .= $curContent . $endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
$fullContent .= $curContent . $endl;
|
||||
}
|
||||
}
|
||||
|
||||
if ($type == 'js')
|
||||
{
|
||||
if (!self::ONLY_MERGE_JS)
|
||||
{
|
||||
$packed = JSMin::minify($fullContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
$packed = $fullContent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$less = new lessc();
|
||||
$less->importDir = BASEPATH . 'i/css/';
|
||||
$fullContent = $less->parse($fullContent);
|
||||
|
||||
if (!self::ONLY_MERGE_CSS)
|
||||
{
|
||||
$cssPacker = new CssPacker($fullContent);
|
||||
$packed = $cssPacker->Pack();
|
||||
}
|
||||
else
|
||||
{
|
||||
$packed = $fullContent;
|
||||
}
|
||||
}
|
||||
|
||||
$packed = $type == 'js' ? ($packed . $fullExeptionContent) : ($fullExeptionContent . $packed);
|
||||
$packed = $type == 'js' ? $this->PrepareJsContent($packed) : $this->PrepareCssContent($packed); // Дополнительно обработать контент пользовательской функцией (если есть)
|
||||
|
||||
self::WriteFile($type == 'js' ? $this->addrJsCacheFile : $this->addrCssCacheFile, $packed);
|
||||
self::WriteFile(($type == 'js' ? $this->addrJsCacheFile : $this->addrCssCacheFile) . $this->gzipPostfix, gzencode($packed, self::COEF_GZIP_COMPRESS));
|
||||
|
||||
if ($this->pFuncPostWriteCache)
|
||||
{
|
||||
call_user_func($this->pFuncPostWriteCache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Если массив now содержиться в storage то перепаковывать не надо т.к. все запакованные данные есть
|
||||
*/
|
||||
private function NeedRePack($storage, $now)
|
||||
{
|
||||
if (self::ALWAYS_REPACK) return true;
|
||||
|
||||
$ret = false;
|
||||
|
||||
foreach ($now as $v)
|
||||
{
|
||||
if (!in_array($v, $storage))
|
||||
{
|
||||
$ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function JsPack($html)
|
||||
{
|
||||
$arrExeptions = $this->arrExeptions_js;
|
||||
$arrExeptionsNotAdd = $this->arrExeptionsNotAdd_js;
|
||||
|
||||
$s = preg_replace('/<!--.*-->/Uis', '', $html);
|
||||
$m = array();
|
||||
preg_match("~<head.*?>(.*?)</head>~is", $s, $m);
|
||||
if (empty($m))
|
||||
{
|
||||
return $html;
|
||||
}
|
||||
|
||||
$s = $m[1];
|
||||
$m = array();
|
||||
preg_match_all("~<script.*?src=['\"](.*?).js['\"].*?></script>~", $s, $m);
|
||||
if (empty($m))
|
||||
{
|
||||
return $html;
|
||||
}
|
||||
|
||||
$arrJsFiles = array();
|
||||
foreach ($m[1] as $k => $jsAddr)
|
||||
{
|
||||
$jsAddr = $jsAddr . ".js";
|
||||
if (in_array($jsAddr, $arrExeptionsNotAdd))
|
||||
{
|
||||
unset($m[1][$k]);
|
||||
continue;
|
||||
}
|
||||
$html = str_ireplace($m[0][$k], "", $html);
|
||||
$jsAddr = call_user_func($this->pGetPathJsFileFromUrl, $jsAddr);
|
||||
$whenModify = filemtime($jsAddr);
|
||||
$arrJsFiles[] = array('time' => $whenModify, 'addr' => $jsAddr);
|
||||
}
|
||||
|
||||
$arrJsFiles = $this->UnickList($arrJsFiles);
|
||||
|
||||
if (file_exists($this->addrJsCacheFileInfo))
|
||||
{
|
||||
$strInfoJsFiles = file_get_contents($this->addrJsCacheFileInfo);
|
||||
$arrInfoJsFiles = unserialize($strInfoJsFiles);
|
||||
if ($this->NeedRePack($arrInfoJsFiles, $arrJsFiles))
|
||||
{
|
||||
if (self::ALWAYS_INDEPENDENT_PACK)
|
||||
{
|
||||
$arrInfoJsFiles = array();
|
||||
}
|
||||
$arrAllJsFiles = self::AUnique(self::AMerge($arrInfoJsFiles, $arrJsFiles));
|
||||
self::WriteDataInCache('js', $arrAllJsFiles, $arrExeptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->flgUseTransSystem)
|
||||
{
|
||||
$this->addrJsCacheFile = str_replace(".js", self::GetTrans('js') . ".js", $this->addrJsCacheFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self::WriteDataInCache('js', $arrJsFiles , $arrExeptions);
|
||||
}
|
||||
|
||||
$includeFileAddr = ($this->flgBuffering && $this->flgUseGZIP) ? $this->addrJsCacheFile.$this->gzipPostfix : $this->addrJsCacheFile;
|
||||
|
||||
$inc = call_user_func($this->pFuncGetAddrJsPackFile, $includeFileAddr);
|
||||
$html = str_ireplace(self::TAG_EXTRAPACKER, self::TAG_EXTRAPACKER . '<script type="text/javascript" charset="UTF-8" src="' . $inc . '"></script>', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private static function AUnique($arr)
|
||||
{
|
||||
$a = array();
|
||||
foreach ($arr as $v)
|
||||
{
|
||||
$a[$v['addr']] = $v;
|
||||
}
|
||||
return array_values($a);
|
||||
}
|
||||
|
||||
// По сути тот же самый array_merge
|
||||
private static function AMerge()
|
||||
{
|
||||
$arrs = func_get_args(); // Массивы которые нужно слить в один
|
||||
$eaxA = array(); // Результирующий массив
|
||||
$eaxAddrsOnly = array(); // Массив только адресов к файлам (нужен просто для более локаничного вычисления)
|
||||
|
||||
foreach ($arrs as $arr)
|
||||
{
|
||||
foreach ($arr as $canBeElem)
|
||||
{
|
||||
// Если текущий файл уже был добавлен
|
||||
if (in_array($canBeElem['addr'], $eaxAddrsOnly))
|
||||
{
|
||||
// То находим его и проверяем, если время его изменения в новом массиве больше старого то заменяем, иначе ничего не делаем
|
||||
foreach ($eaxA as $k => $v)
|
||||
{
|
||||
if ($v['addr'] == $canBeElem['addr'])
|
||||
{
|
||||
if ($v['time'] < $canBeElem['time'])
|
||||
{
|
||||
$eaxA[$k] = $canBeElem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Если же этого файла нет в массиве то добавляем его следущим
|
||||
else
|
||||
{
|
||||
$eaxA[] = $canBeElem;
|
||||
$eaxAddrsOnly[] = $canBeElem['addr'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $eaxA;
|
||||
}
|
||||
|
||||
private function CssPack($html)
|
||||
{
|
||||
$arrExeptions = $this->arrExeptions_css;
|
||||
$arrExeptionsNotAdd = $this->arrExeptionsNotAdd_css;
|
||||
|
||||
$s = preg_replace('/<!--.*-->/Uis', '', $html);
|
||||
$m = array();
|
||||
preg_match("~<head.*?>(.*?)</head>~is", $s, $m);
|
||||
if (empty($m))
|
||||
{
|
||||
return $html;
|
||||
}
|
||||
|
||||
$s = $m[1];
|
||||
$m = array();
|
||||
preg_match_all("~<link.*?href=['\"](.*?)\.(css|less)['\"].*?>~", $s, $m);
|
||||
if (empty($m))
|
||||
{
|
||||
return $html;
|
||||
}
|
||||
|
||||
$arrCssFiles = array();
|
||||
foreach ($m[1] as $k => $cssAddr)
|
||||
{
|
||||
$ext = $m[2][$k];
|
||||
$cssAddr .= ".{$ext}";
|
||||
if (in_array($cssAddr, $arrExeptionsNotAdd))
|
||||
{
|
||||
unset($m[1][$k]);
|
||||
continue;
|
||||
}
|
||||
$html = str_ireplace($m[0][$k], "", $html);
|
||||
$cssAddr = call_user_func($this->pGetPathCssFileFromUrl, $cssAddr);
|
||||
$whenModify = filemtime($cssAddr);
|
||||
$arrCssFiles[] = array('time' => $whenModify, 'addr' => $cssAddr);
|
||||
}
|
||||
|
||||
$arrCssFiles = $this->UnickList($arrCssFiles);
|
||||
|
||||
if (file_exists($this->addrCssCacheFileInfo))
|
||||
{
|
||||
$strInfoCssFiles = file_get_contents($this->addrCssCacheFileInfo);
|
||||
$arrInfoCssFiles = unserialize($strInfoCssFiles);
|
||||
if ($this->NeedRePack($arrInfoCssFiles, $arrCssFiles))
|
||||
{
|
||||
if (self::ALWAYS_INDEPENDENT_PACK)
|
||||
{
|
||||
$arrInfoCssFiles = array();
|
||||
}
|
||||
$arrAllCssFiles = self::AUnique(self::AMerge($arrInfoCssFiles, $arrCssFiles));
|
||||
self::WriteDataInCache('css', $arrAllCssFiles, $arrExeptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->flgUseTransSystem)
|
||||
{
|
||||
$this->addrCssCacheFile = str_replace(".css", self::GetTrans('css') . ".css", $this->addrCssCacheFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self::WriteDataInCache('css', $arrCssFiles , $arrExeptions);
|
||||
}
|
||||
|
||||
$includeFileAddr = ($this->flgBuffering && $this->flgUseGZIP) ? $this->addrCssCacheFile.$this->gzipPostfix : $this->addrCssCacheFile;
|
||||
|
||||
$inc = call_user_func($this->pFuncGetAddrCssPackFile, $includeFileAddr);
|
||||
$html = str_ireplace(self::TAG_EXTRAPACKER, self::TAG_EXTRAPACKER . '<link rel="stylesheet" charset="UTF-8" type="text/css" href="' . $inc . '" />', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function CanUseGZIP()
|
||||
{
|
||||
$httpAcceptEncoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : '';
|
||||
$hasGzip = strstr($httpAcceptEncoding, 'gzip') !== false && extension_loaded('zlib');
|
||||
$canEncoding = true;
|
||||
|
||||
if (!isset($_SERVER['HTTP_USER_AGENT']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
if (stripos($_SERVER['HTTP_USER_AGENT'], 'safari') === false)
|
||||
{
|
||||
if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera')
|
||||
&&
|
||||
preg_match('/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches))
|
||||
{
|
||||
if (isset($matches[1]))
|
||||
{
|
||||
$version = floatval($matches[1]);
|
||||
|
||||
if ($version < 6)
|
||||
{
|
||||
$canEncoding = false;
|
||||
}
|
||||
|
||||
if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
|
||||
{
|
||||
$canEncoding = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$canEncoding = false;
|
||||
}
|
||||
|
||||
return $canEncoding && $hasGzip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция запаковки контента в зависимости от параметров пакера
|
||||
*
|
||||
* @param string html-контент документа в котором содержится head с подключенными js-ками и css-ами
|
||||
*/
|
||||
public function Pack($html)
|
||||
{
|
||||
// Если нету тега для замены ExtraPacker-ом то просто вернуть контент
|
||||
if (strpos($html, self::TAG_EXTRAPACKER) === false)
|
||||
{
|
||||
return $html;
|
||||
}
|
||||
|
||||
if ($this->flgJsPack)
|
||||
{
|
||||
$html = $this->JsPack($html);
|
||||
}
|
||||
if ($this->flgCssPack)
|
||||
{
|
||||
$html = $this->CssPack($html);
|
||||
}
|
||||
if ($this->flgHtmlPack)
|
||||
{
|
||||
$html = $this->HtmlPack($html);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Конструктор объекта
|
||||
*
|
||||
* @param pointer $pGetPathJsFileFromUrl - ссылка на функцию которая получит из url в src js-ки физический путь на диске к этому файлу
|
||||
* @param pointer $pGetPathCssFileFromUrl - ссылка на функцию которая получит из url в href css-ки физический путь на диске к этому файлу
|
||||
* @param pointer $pFuncGetAddrJsPackFile - ссылка на фунцию которая получит из path на диске спакованного js-файла url который подставится в новый src спакованной js-ки
|
||||
* @param pointer $pFuncGetAddrCssPackFile - ссылка на фунцию которая получит из path на диске спакованного css-файла url который подставится в новый href спакованной css-ки
|
||||
* @param pointer $pFuncPreWriteCache - указатель на функцию выполняемую до начала работы по созданию нового кеш-файла (обычно блокировка сайта)
|
||||
* @param pointer $pFuncPostWriteCache - указатель на функцию выполняемую по завершению работы и записи кеш-файла (обычно разблокировка сайта)
|
||||
* @param string $addrJsCacheFileInfo - путь к временному файлу с информацией о спакованных js файлах в текущей транзакции
|
||||
* @param string $addrJsCacheFile - путь к временному кеш файлу спакованных js-ок (GZIP будут с таким же именем только префикс из настроек добавится)
|
||||
* @param string $addrCssCacheFileInfo - путь к временному файлу с информацией о спакованных css файлах в текущей транзакции
|
||||
* @param string $addrCssCacheFile - путь к временному кеш файлу спакованных css-ок (GZIP будут с таким же именем только префикс из настроек добавится)
|
||||
* @param bool $flgHtmlPack - флаг включения html-запаковки (могут быть проблемы если используете js-подстветки кода сайте т.к. в начале удалятся пробелы и табуляция)
|
||||
* @param bool $flgCssPack - флаг включения css-запаковки
|
||||
* @param bool $flgJsPack - флаг включения js-запаковки
|
||||
* @param array $arrExeptions_js - массив исключений 1-го порядка (url-ы как и src), данные файлы прибавятся склеенному в конец, но не будут поковаться
|
||||
* @param array $arrExeptionsNotAdd_js - массив исключений 2-го порядка - вообще не добавятся в запокованный файл
|
||||
* @param array $arrExeptions_css - массив исключений 1-го порядка (url-ы как и href), данные файлы прибавятся склеенному в конец, но не будут поковаться
|
||||
* @param array $arrExeptionsNotAdd_css - массив исключений 2-го порядка - вообще не добавятся в запокованный файл
|
||||
* @param bool $flgUseTransSystem - использоватьс систему транзакций (в url запакованного файла ставится № говорящий о текущей транзакции)
|
||||
* @param string $addrNumJsTrans - путь к файлу содержащий № js транзакций
|
||||
* @param string $addrNumCssTrans - путь к файлу содержащий № css транзакций
|
||||
* @param bool $flgBuffering - включить авто-определение GZIP буферизации для браузеров
|
||||
*/
|
||||
public function __construct(
|
||||
$pGetPathJsFileFromUrl,
|
||||
$pGetPathCssFileFromUrl,
|
||||
$pFuncGetAddrJsPackFile,
|
||||
$pFuncGetAddrCssPackFile,
|
||||
$pFuncPreWriteCache = NULL,
|
||||
$pFuncPostWriteCache = NULL,
|
||||
$addrJsCacheFileInfo = '',
|
||||
$addrJsCacheFile = '',
|
||||
$addrCssCacheFileInfo = '',
|
||||
$addrCssCacheFile = '',
|
||||
$flgHtmlPack = true,
|
||||
$flgCssPack = true,
|
||||
$flgJsPack = true,
|
||||
$arrExeptions_js = array(),
|
||||
$arrExeptionsNotAdd_js = array(),
|
||||
$arrExeptions_css = array(),
|
||||
$arrExeptionsNotAdd_css = array(),
|
||||
$flgUseTransSystem = true,
|
||||
$addrNumJsTrans = '',
|
||||
$addrNumCssTrans = '',
|
||||
$flgBuffering = true,
|
||||
$pPrepareEachFile = NULL,
|
||||
$pCallBackFunctionCSS = NULL,
|
||||
$pCallBackFunctionJS = NULL
|
||||
)
|
||||
{
|
||||
$this->pGetPathJsFileFromUrl = $pGetPathJsFileFromUrl;
|
||||
$this->pGetPathCssFileFromUrl = $pGetPathCssFileFromUrl;
|
||||
|
||||
$this->pFuncGetAddrJsPackFile = $pFuncGetAddrJsPackFile;
|
||||
$this->pFuncGetAddrCssPackFile = $pFuncGetAddrCssPackFile;
|
||||
|
||||
$this->pFuncPreWriteCache = $pFuncPreWriteCache;
|
||||
$this->pFuncPostWriteCache = $pFuncPostWriteCache;
|
||||
|
||||
$defaultDir = str_replace("\\", "/", dirname(__FILE__)) . '/tmp/';
|
||||
$this->addrJsCacheFileInfo = $addrJsCacheFileInfo ? $addrJsCacheFileInfo : ($defaultDir . "js/cache/info.txt");
|
||||
$this->addrJsCacheFile = $addrJsCacheFile ? $addrJsCacheFile : ($defaultDir . "js/cache/js.js");
|
||||
$this->addrCssCacheFileInfo = $addrCssCacheFileInfo ? $addrCssCacheFileInfo : ($defaultDir . "css/cache/info.txt");
|
||||
$this->addrCssCacheFile = $addrCssCacheFile ? $addrCssCacheFile : ($defaultDir . "css/cache/css.css");
|
||||
|
||||
$this->flgHtmlPack = $flgHtmlPack;
|
||||
$this->flgCssPack = $flgCssPack;
|
||||
$this->flgJsPack = $flgJsPack;
|
||||
|
||||
$this->arrExeptions_js = $arrExeptions_js;
|
||||
$this->arrExeptionsNotAdd_js = $arrExeptionsNotAdd_js;
|
||||
|
||||
$this->arrExeptions_css = $arrExeptions_css;
|
||||
$this->arrExeptionsNotAdd_css = $arrExeptionsNotAdd_css;
|
||||
|
||||
$this->flgUseTransSystem = $flgUseTransSystem;
|
||||
$this->addrNumJsTrans = $addrNumJsTrans ? $addrNumJsTrans : ($defaultDir . 'js/cache/trans.txt');
|
||||
$this->addrNumCssTrans = $addrNumCssTrans ? $addrNumCssTrans : ($defaultDir . 'css/cache/trans.txt');
|
||||
|
||||
$this->flgBuffering = $flgBuffering;
|
||||
$this->flgUseGZIP = self::CanUseGZIP();
|
||||
|
||||
$this->pPrepareEachFile = $pPrepareEachFile;
|
||||
$this->pCallBackFunctionCSS = $pCallBackFunctionCSS;
|
||||
$this->pCallBackFunctionJS = $pCallBackFunctionJS;
|
||||
}
|
||||
};
|
||||
?>
|
||||
25
lib/ExtraPacker/Lib/CssPacker.php
Executable file
25
lib/ExtraPacker/Lib/CssPacker.php
Executable file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__FILE__) . "/cssmin-v1.0.1.b3.php";
|
||||
|
||||
|
||||
/**
|
||||
* Пакеровщик css файлов
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class CssPacker
|
||||
{
|
||||
private $content;
|
||||
|
||||
public function __construct($c)
|
||||
{
|
||||
$this->content = $c;
|
||||
}
|
||||
|
||||
public function Pack()
|
||||
{
|
||||
return cssmin::minify($this->content);
|
||||
}
|
||||
};
|
||||
?>
|
||||
258
lib/ExtraPacker/Lib/HtmlPacker.php
Executable file
258
lib/ExtraPacker/Lib/HtmlPacker.php
Executable file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_HTML
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress HTML
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary comments and
|
||||
* tokens. IE conditional comments are preserved. There are also options to have
|
||||
* STYLE and SCRIPT blocks compressed by callback functions.
|
||||
*
|
||||
* A test suite is available.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_HTML {
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $_jsCleanComments = true;
|
||||
|
||||
/**
|
||||
* "Minify" an HTML page
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($html, $options = array()) {
|
||||
$min = new self($html, $options);
|
||||
return $min->process();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a minifier object
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($html, $options = array())
|
||||
{
|
||||
$this->_html = str_replace("\r\n", "\n", trim($html));
|
||||
if (isset($options['xhtml'])) {
|
||||
$this->_isXhtml = (bool)$options['xhtml'];
|
||||
}
|
||||
if (isset($options['cssMinifier'])) {
|
||||
$this->_cssMinifier = $options['cssMinifier'];
|
||||
}
|
||||
if (isset($options['jsMinifier'])) {
|
||||
$this->_jsMinifier = $options['jsMinifier'];
|
||||
}
|
||||
if (isset($options['jsCleanComments'])) {
|
||||
$this->_jsCleanComments = (bool)$options['jsCleanComments'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minify the markeup given in the constructor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
if ($this->_isXhtml === null) {
|
||||
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
|
||||
}
|
||||
|
||||
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
|
||||
$this->_placeholders = array();
|
||||
|
||||
// replace SCRIPTs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
|
||||
,array($this, '_removeScriptCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace STYLEs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
|
||||
,array($this, '_removeStyleCB')
|
||||
,$this->_html);
|
||||
|
||||
// remove HTML comments (not containing IE conditional comments).
|
||||
$this->_html = preg_replace_callback(
|
||||
'/<!--([\\s\\S]*?)-->/'
|
||||
,array($this, '_commentCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace PREs with placeholders
|
||||
$this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
|
||||
,array($this, '_removePreCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace TEXTAREAs with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
|
||||
,array($this, '_removeTextareaCB')
|
||||
,$this->_html);
|
||||
|
||||
// trim each line.
|
||||
// @todo take into account attribute values that span multiple lines.
|
||||
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
|
||||
|
||||
// remove ws around block/undisplayed elements
|
||||
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
|
||||
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
|
||||
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
|
||||
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
|
||||
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
|
||||
|
||||
// remove ws outside of all elements
|
||||
$this->_html = preg_replace(
|
||||
'/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
|
||||
,'>$1$2$3<'
|
||||
,$this->_html);
|
||||
|
||||
// use newlines before 1st attribute in open tags (to limit line lengths)
|
||||
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
|
||||
|
||||
// fill placeholders
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
// issue 229: multi-pass to catch scripts that didn't get replaced in textareas
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
return $this->_html;
|
||||
}
|
||||
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
|
||||
? $m[0]
|
||||
: '';
|
||||
}
|
||||
|
||||
protected function _reservePlace($content)
|
||||
{
|
||||
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
|
||||
$this->_placeholders[$placeholder] = $content;
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
protected $_isXhtml = null;
|
||||
protected $_replacementHash = null;
|
||||
protected $_placeholders = array();
|
||||
protected $_cssMinifier = null;
|
||||
protected $_jsMinifier = null;
|
||||
|
||||
protected function _removePreCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<pre{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeTextareaCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<textarea{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeStyleCB($m)
|
||||
{
|
||||
$openStyle = "<style{$m[1]}";
|
||||
$css = $m[2];
|
||||
// remove HTML comments
|
||||
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
|
||||
|
||||
// remove CDATA section markers
|
||||
$css = $this->_removeCdata($css);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_cssMinifier
|
||||
? $this->_cssMinifier
|
||||
: 'trim';
|
||||
$css = call_user_func($minifier, $css);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($css)
|
||||
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
|
||||
: "{$openStyle}{$css}</style>"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeScriptCB($m)
|
||||
{
|
||||
$openScript = "<script{$m[2]}";
|
||||
$js = $m[3];
|
||||
|
||||
// whitespace surrounding? preserve at least one space
|
||||
$ws1 = ($m[1] === '') ? '' : ' ';
|
||||
$ws2 = ($m[4] === '') ? '' : ' ';
|
||||
|
||||
// remove HTML comments (and ending "//" if present)
|
||||
if ($this->_jsCleanComments) {
|
||||
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
|
||||
}
|
||||
|
||||
// remove CDATA section markers
|
||||
$js = $this->_removeCdata($js);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_jsMinifier
|
||||
? $this->_jsMinifier
|
||||
: 'trim';
|
||||
$js = call_user_func($minifier, $js);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($js)
|
||||
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
|
||||
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeCdata($str)
|
||||
{
|
||||
return (false !== strpos($str, '<![CDATA['))
|
||||
? str_replace(array('<![CDATA[', ']]>'), '', $str)
|
||||
: $str;
|
||||
}
|
||||
|
||||
protected function _needsCdata($str)
|
||||
{
|
||||
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
|
||||
}
|
||||
};
|
||||
?>
|
||||
438
lib/ExtraPacker/Lib/JSMin.php
Executable file
438
lib/ExtraPacker/Lib/JSMin.php
Executable file
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
/**
|
||||
* JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
|
||||
*
|
||||
* <code>
|
||||
* $minifiedJs = JSMin::minify($js);
|
||||
* </code>
|
||||
*
|
||||
* This is a modified port of jsmin.c. Improvements:
|
||||
*
|
||||
* Does not choke on some regexp literals containing quote characters. E.g. /'/
|
||||
*
|
||||
* Spaces are preserved after some add/sub operators, so they are not mistakenly
|
||||
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b
|
||||
*
|
||||
* Preserves multi-line comments that begin with /*!
|
||||
*
|
||||
* PHP 5 or higher is required.
|
||||
*
|
||||
* Permission is hereby granted to use this version of the library under the
|
||||
* same terms as jsmin.c, which has the following license:
|
||||
*
|
||||
* --
|
||||
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
|
||||
*
|
||||
* 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 shall be used for Good, not Evil.
|
||||
*
|
||||
* 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.
|
||||
* --
|
||||
*
|
||||
* @package JSMin
|
||||
* @author Ryan Grove <ryan@wonko.com> (PHP port)
|
||||
* @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
|
||||
* @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
|
||||
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
|
||||
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
|
||||
* @license http://opensource.org/licenses/mit-license.php MIT License
|
||||
* @link http://code.google.com/p/jsmin-php/
|
||||
*/
|
||||
|
||||
class JSMin {
|
||||
const ORD_LF = 10;
|
||||
const ORD_SPACE = 32;
|
||||
const ACTION_KEEP_A = 1;
|
||||
const ACTION_DELETE_A = 2;
|
||||
const ACTION_DELETE_A_B = 3;
|
||||
|
||||
protected $a = "\n";
|
||||
protected $b = '';
|
||||
protected $input = '';
|
||||
protected $inputIndex = 0;
|
||||
protected $inputLength = 0;
|
||||
protected $lookAhead = null;
|
||||
protected $output = '';
|
||||
protected $lastByteOut = '';
|
||||
protected $keptComment = '';
|
||||
|
||||
/**
|
||||
* Minify Javascript.
|
||||
*
|
||||
* @param string $js Javascript to be minified
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($js)
|
||||
{
|
||||
$jsmin = new JSMin($js);
|
||||
return $jsmin->min();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
*/
|
||||
public function __construct($input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform minification, return result
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function min()
|
||||
{
|
||||
if ($this->output !== '') { // min already run
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
$mbIntEnc = null;
|
||||
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||
$mbIntEnc = mb_internal_encoding();
|
||||
mb_internal_encoding('8bit');
|
||||
}
|
||||
$this->input = str_replace("\r\n", "\n", $this->input);
|
||||
$this->inputLength = strlen($this->input);
|
||||
|
||||
$this->action(self::ACTION_DELETE_A_B);
|
||||
|
||||
while ($this->a !== null) {
|
||||
// determine next command
|
||||
$command = self::ACTION_KEEP_A; // default
|
||||
if ($this->a === ' ') {
|
||||
if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
|
||||
&& ($this->b === $this->lastByteOut)) {
|
||||
// Don't delete this space. If we do, the addition/subtraction
|
||||
// could be parsed as a post-increment
|
||||
} elseif (! $this->isAlphaNum($this->b)) {
|
||||
$command = self::ACTION_DELETE_A;
|
||||
}
|
||||
} elseif ($this->a === "\n") {
|
||||
if ($this->b === ' ') {
|
||||
$command = self::ACTION_DELETE_A_B;
|
||||
|
||||
// in case of mbstring.func_overload & 2, must check for null b,
|
||||
// otherwise mb_strpos will give WARNING
|
||||
} elseif ($this->b === null
|
||||
|| (false === strpos('{[(+-!~', $this->b)
|
||||
&& ! $this->isAlphaNum($this->b))) {
|
||||
$command = self::ACTION_DELETE_A;
|
||||
}
|
||||
} elseif (! $this->isAlphaNum($this->a)) {
|
||||
if ($this->b === ' '
|
||||
|| ($this->b === "\n"
|
||||
&& (false === strpos('}])+-"\'', $this->a)))) {
|
||||
$command = self::ACTION_DELETE_A_B;
|
||||
}
|
||||
}
|
||||
$this->action($command);
|
||||
}
|
||||
$this->output = trim($this->output);
|
||||
|
||||
if ($mbIntEnc !== null) {
|
||||
mb_internal_encoding($mbIntEnc);
|
||||
}
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
|
||||
* ACTION_DELETE_A = Copy B to A. Get the next B.
|
||||
* ACTION_DELETE_A_B = Get the next B.
|
||||
*
|
||||
* @param int $command
|
||||
* @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
|
||||
*/
|
||||
protected function action($command)
|
||||
{
|
||||
// make sure we don't compress "a + ++b" to "a+++b", etc.
|
||||
if ($command === self::ACTION_DELETE_A_B
|
||||
&& $this->b === ' '
|
||||
&& ($this->a === '+' || $this->a === '-')) {
|
||||
// Note: we're at an addition/substraction operator; the inputIndex
|
||||
// will certainly be a valid index
|
||||
if ($this->input[$this->inputIndex] === $this->a) {
|
||||
// This is "+ +" or "- -". Don't delete the space.
|
||||
$command = self::ACTION_KEEP_A;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($command) {
|
||||
case self::ACTION_KEEP_A: // 1
|
||||
$this->output .= $this->a;
|
||||
|
||||
if ($this->keptComment) {
|
||||
$this->output = rtrim($this->output, "\n");
|
||||
$this->output .= $this->keptComment;
|
||||
$this->keptComment = '';
|
||||
}
|
||||
|
||||
$this->lastByteOut = $this->a;
|
||||
|
||||
// fallthrough intentional
|
||||
case self::ACTION_DELETE_A: // 2
|
||||
$this->a = $this->b;
|
||||
if ($this->a === "'" || $this->a === '"') { // string literal
|
||||
$str = $this->a; // in case needed for exception
|
||||
for(;;) {
|
||||
$this->output .= $this->a;
|
||||
$this->lastByteOut = $this->a;
|
||||
|
||||
$this->a = $this->get();
|
||||
if ($this->a === $this->b) { // end quote
|
||||
break;
|
||||
}
|
||||
if ($this->isEOF($this->a)) {
|
||||
throw new JSMin_UnterminatedStringException(
|
||||
"JSMin: Unterminated String at byte {$this->inputIndex}: {$str}");
|
||||
}
|
||||
$str .= $this->a;
|
||||
if ($this->a === '\\') {
|
||||
$this->output .= $this->a;
|
||||
$this->lastByteOut = $this->a;
|
||||
|
||||
$this->a = $this->get();
|
||||
$str .= $this->a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallthrough intentional
|
||||
case self::ACTION_DELETE_A_B: // 3
|
||||
$this->b = $this->next();
|
||||
if ($this->b === '/' && $this->isRegexpLiteral()) {
|
||||
$this->output .= $this->a . $this->b;
|
||||
$pattern = '/'; // keep entire pattern in case we need to report it in the exception
|
||||
for(;;) {
|
||||
$this->a = $this->get();
|
||||
$pattern .= $this->a;
|
||||
if ($this->a === '[') {
|
||||
for(;;) {
|
||||
$this->output .= $this->a;
|
||||
$this->a = $this->get();
|
||||
$pattern .= $this->a;
|
||||
if ($this->a === ']') {
|
||||
break;
|
||||
}
|
||||
if ($this->a === '\\') {
|
||||
$this->output .= $this->a;
|
||||
$this->a = $this->get();
|
||||
$pattern .= $this->a;
|
||||
}
|
||||
if ($this->isEOF($this->a)) {
|
||||
throw new JSMin_UnterminatedRegExpException(
|
||||
"JSMin: Unterminated set in RegExp at byte "
|
||||
. $this->inputIndex .": {$pattern}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->a === '/') { // end pattern
|
||||
break; // while (true)
|
||||
} elseif ($this->a === '\\') {
|
||||
$this->output .= $this->a;
|
||||
$this->a = $this->get();
|
||||
$pattern .= $this->a;
|
||||
} elseif ($this->isEOF($this->a)) {
|
||||
throw new JSMin_UnterminatedRegExpException(
|
||||
"JSMin: Unterminated RegExp at byte {$this->inputIndex}: {$pattern}");
|
||||
}
|
||||
$this->output .= $this->a;
|
||||
$this->lastByteOut = $this->a;
|
||||
}
|
||||
$this->b = $this->next();
|
||||
}
|
||||
// end case ACTION_DELETE_A_B
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isRegexpLiteral()
|
||||
{
|
||||
if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
|
||||
// we obviously aren't dividing
|
||||
return true;
|
||||
}
|
||||
if ($this->a === ' ' || $this->a === "\n") {
|
||||
$length = strlen($this->output);
|
||||
if ($length < 2) { // weird edge case
|
||||
return true;
|
||||
}
|
||||
// you can't divide a keyword
|
||||
if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) {
|
||||
if ($this->output === $m[0]) { // odd but could happen
|
||||
return true;
|
||||
}
|
||||
// make sure it's a keyword, not end of an identifier
|
||||
$charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1);
|
||||
if (! $this->isAlphaNum($charBeforeKeyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next character from stdin. Watch out for lookahead. If the character is a control character,
|
||||
* translate it to a space or linefeed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get()
|
||||
{
|
||||
$c = $this->lookAhead;
|
||||
$this->lookAhead = null;
|
||||
if ($c === null) {
|
||||
// getc(stdin)
|
||||
if ($this->inputIndex < $this->inputLength) {
|
||||
$c = $this->input[$this->inputIndex];
|
||||
$this->inputIndex += 1;
|
||||
} else {
|
||||
$c = null;
|
||||
}
|
||||
}
|
||||
if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) {
|
||||
return $c;
|
||||
}
|
||||
if ($c === "\r") {
|
||||
return "\n";
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Does $a indicate end of input?
|
||||
*
|
||||
* @param string $a
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEOF($a)
|
||||
{
|
||||
return ord($a) <= self::ORD_LF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next char (without getting it). If is ctrl character, translate to a space or newline.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function peek()
|
||||
{
|
||||
$this->lookAhead = $this->get();
|
||||
return $this->lookAhead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
|
||||
*
|
||||
* @param string $c
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAlphaNum($c)
|
||||
{
|
||||
return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume a single line comment from input (possibly retaining it)
|
||||
*/
|
||||
protected function consumeSingleLineComment()
|
||||
{
|
||||
$comment = '';
|
||||
while (true) {
|
||||
$get = $this->get();
|
||||
$comment .= $get;
|
||||
if (ord($get) <= self::ORD_LF) { // end of line reached
|
||||
// if IE conditional comment
|
||||
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
|
||||
$this->keptComment .= "/{$comment}";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume a multiple line comment from input (possibly retaining it)
|
||||
*
|
||||
* @throws JSMin_UnterminatedCommentException
|
||||
*/
|
||||
protected function consumeMultipleLineComment()
|
||||
{
|
||||
$this->get();
|
||||
$comment = '';
|
||||
for(;;) {
|
||||
$get = $this->get();
|
||||
if ($get === '*') {
|
||||
if ($this->peek() === '/') { // end of comment reached
|
||||
$this->get();
|
||||
if (0 === strpos($comment, '!')) {
|
||||
// preserved by YUI Compressor
|
||||
if (!$this->keptComment) {
|
||||
// don't prepend a newline if two comments right after one another
|
||||
$this->keptComment = "\n";
|
||||
}
|
||||
$this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
|
||||
} else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
|
||||
// IE conditional
|
||||
$this->keptComment .= "/*{$comment}*/";
|
||||
}
|
||||
return;
|
||||
}
|
||||
} elseif ($get === null) {
|
||||
throw new JSMin_UnterminatedCommentException(
|
||||
"JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
|
||||
}
|
||||
$comment .= $get;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next character, skipping over comments. Some comments may be preserved.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function next()
|
||||
{
|
||||
$get = $this->get();
|
||||
if ($get === '/') {
|
||||
switch ($this->peek()) {
|
||||
case '/':
|
||||
$this->consumeSingleLineComment();
|
||||
$get = "\n";
|
||||
break;
|
||||
case '*':
|
||||
$this->consumeMultipleLineComment();
|
||||
$get = ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $get;
|
||||
}
|
||||
}
|
||||
|
||||
class JSMin_UnterminatedStringException extends Exception {}
|
||||
class JSMin_UnterminatedCommentException extends Exception {}
|
||||
class JSMin_UnterminatedRegExpException extends Exception {}
|
||||
?>
|
||||
206
lib/ExtraPacker/Lib/cssmin-v1.0.1.b3.php
Executable file
206
lib/ExtraPacker/Lib/cssmin-v1.0.1.b3.php
Executable file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* cssmin.php - A simple CSS minifier.
|
||||
* --
|
||||
*
|
||||
* <code>
|
||||
* include("cssmin.php");
|
||||
* file_put_contents("path/to/target.css", cssmin::minify(file_get_contents("path/to/source.css")));
|
||||
* </code>
|
||||
* --
|
||||
*
|
||||
* 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.
|
||||
* --
|
||||
*
|
||||
* @package cssmin
|
||||
* @author Joe Scylla <joe.scylla@gmail.com>
|
||||
* @copyright 2008 Joe Scylla <joe.scylla@gmail.com>
|
||||
* @license http://opensource.org/licenses/mit-license.php MIT License
|
||||
* @version 1.0.1.b3 (2008-10-02)
|
||||
*/
|
||||
class cssmin
|
||||
{
|
||||
/**
|
||||
* Minifies stylesheet definitions
|
||||
*
|
||||
* <code>
|
||||
* $css_minified = cssmin::minify(file_get_contents("path/to/target/file.css"));
|
||||
* </code>
|
||||
*
|
||||
* @param string $css Stylesheet definitions as string
|
||||
* @param array|string $options Array or comma speperated list of options:
|
||||
*
|
||||
* - remove-last-semicolon: Removes the last semicolon in
|
||||
* the style definition of an element (activated by default).
|
||||
*
|
||||
* - preserve-urls: Preserves every url defined in an url()-
|
||||
* expression. This option is only required if you have
|
||||
* defined really uncommon urls with multiple spaces or
|
||||
* combination of colon, semi-colon, braces with leading or
|
||||
* following spaces.
|
||||
* @return string Minified stylesheet definitions
|
||||
*/
|
||||
public static function minify($css, $options = "remove-last-semicolon")
|
||||
{
|
||||
$options = ($options == "") ? array() : (is_array($options) ? $options : explode(",", $options));
|
||||
if (in_array("preserve-urls", $options))
|
||||
{
|
||||
// Encode url() to base64
|
||||
$css = preg_replace_callback("/url\s*\((.*)\)/siU", "cssmin_encode_url", $css);
|
||||
}
|
||||
// Remove comments
|
||||
$css = preg_replace("/\/\*[\d\D]*?\*\/|\t+/", " ", $css);
|
||||
// Replace CR, LF and TAB to spaces
|
||||
$css = str_replace(array("\n", "\r", "\t"), " ", $css);
|
||||
// Replace multiple to single space
|
||||
$css = preg_replace("/\s\s+/", " ", $css);
|
||||
// Remove unneeded spaces
|
||||
$css = preg_replace("/\s*({|}|\[|\]|=|~|\+|>|\||;|:|,)\s*/", "$1", $css);
|
||||
if (in_array("remove-last-semicolon", $options))
|
||||
{
|
||||
// Removes the last semicolon of every style definition
|
||||
$css = str_replace(";}", "}", $css);
|
||||
}
|
||||
$css = trim($css);
|
||||
if (in_array("preserve-urls", $options))
|
||||
{
|
||||
// Decode url()
|
||||
$css = preg_replace_callback("/url\s*\((.*)\)/siU", "cssmin_encode_url", $css);
|
||||
}
|
||||
return $css;
|
||||
}
|
||||
/**
|
||||
* Return a array structure of a stylesheet definitions.
|
||||
*
|
||||
* <code>
|
||||
* $css_structure = cssmin::toArray(file_get_contents("path/to/target/file.css"));
|
||||
* </code>
|
||||
*
|
||||
* @param string $css Stylesheet definitions as string
|
||||
* @param string $options Options for {@link cssmin::minify()}
|
||||
* @return array Structure of the stylesheet definitions as array
|
||||
*/
|
||||
public static function toArray($css, $options = "")
|
||||
{
|
||||
$r = array();
|
||||
$css = cssmin::minify($css, $options);
|
||||
preg_match_all("/(.+){(.+:.+);}/U", $css, $items);
|
||||
if (count($items[0]) > 0)
|
||||
{
|
||||
for ($i = 0; $i < $c = count($items[0]); $i++)
|
||||
{
|
||||
$keys = explode(",", $items[1][$i]);
|
||||
$styles_tmp = explode(";", $items[2][$i]);
|
||||
$styles = array();
|
||||
foreach ($styles_tmp as $style)
|
||||
{
|
||||
$style_tmp = explode(":", $style);
|
||||
$styles[$style_tmp[0]] = $style_tmp[1];
|
||||
}
|
||||
$r[] = array
|
||||
(
|
||||
"keys" => cssmin_array_clean($keys),
|
||||
"styles" => cssmin_array_clean($styles)
|
||||
);
|
||||
}
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
/**
|
||||
* Return a array structure created by {@link cssmin::toArray()} to a string.
|
||||
*
|
||||
* <code>
|
||||
* $css_string = cssmin::toString($css_structure);
|
||||
* </code>
|
||||
*
|
||||
* @param array $css
|
||||
* @return array
|
||||
*/
|
||||
public static function toString(array $array)
|
||||
{
|
||||
$r = "";
|
||||
foreach ($array as $item)
|
||||
{
|
||||
$r .= implode(",", $item["keys"]) . "{";
|
||||
foreach ($item["styles"] as $key => $value)
|
||||
{
|
||||
$r .= $key . ":" . $value . ";";
|
||||
}
|
||||
$r .= "}";
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims all elements of the array and removes empty elements.
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
function cssmin_array_clean(array $array)
|
||||
{
|
||||
$r = array();
|
||||
$c = count($v);
|
||||
if (cssmin_array_is_assoc($array))
|
||||
{
|
||||
foreach ($array as $key => $value)
|
||||
{
|
||||
$r[$key] = trim($value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($array as $value)
|
||||
{
|
||||
if (trim($value) != "")
|
||||
{
|
||||
$r[] = trim($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
/**
|
||||
* Return if a value is a associative array.
|
||||
*
|
||||
* @param array $array
|
||||
* @return bool
|
||||
*/
|
||||
function cssmin_array_is_assoc($array)
|
||||
{
|
||||
if (!is_array($array))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
krsort($array, SORT_STRING);
|
||||
return !is_numeric(key($array));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encodes a url() expression.
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
function cssmin_encode_url($match)
|
||||
{
|
||||
return "url(" . base64_encode(trim($match[1])) . ")";
|
||||
}
|
||||
/**
|
||||
* Decodes a url() expression.
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
function cssmin_decode_url($match)
|
||||
{
|
||||
return "url(" . base64_decode($match[1]) . ")";
|
||||
}
|
||||
?>
|
||||
3675
lib/ExtraPacker/Lib/lessc.inc.php
Executable file
3675
lib/ExtraPacker/Lib/lessc.inc.php
Executable file
File diff suppressed because it is too large
Load Diff
133
lib/FileLogger.php
Executable file
133
lib/FileLogger.php
Executable file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс ведения статистики в файл
|
||||
*
|
||||
* Предназначен для создания файлов логирования
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class FileLogger
|
||||
{
|
||||
private static $loggers = array();
|
||||
|
||||
private $time;
|
||||
private $aLog;
|
||||
private $fPath;
|
||||
private $fHandle;
|
||||
|
||||
const LOG_SEPARATOR = PHP_EOL;
|
||||
const MAXSIZE_LOGFILE = 10485760; // 10mb
|
||||
const FLG_CREATE_ARCHIVE = true;
|
||||
|
||||
private function __construct($fPath, $flgAppendData = true)
|
||||
{
|
||||
$this->fPath = $fPath;
|
||||
$dirs = dirname($fPath);
|
||||
|
||||
if ($dirs)
|
||||
{
|
||||
FileSys::MakeDir($dirs);
|
||||
}
|
||||
|
||||
if (!$flgAppendData)
|
||||
{
|
||||
fclose(fopen($fPath, 'a+'));
|
||||
$this->fHandle = fopen($fPath, 'r+');
|
||||
ftruncate($this->fHandle, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file_exists($fPath))
|
||||
{
|
||||
if (filesize($fPath) >= self::MAXSIZE_LOGFILE)
|
||||
{
|
||||
if (self::FLG_CREATE_ARCHIVE)
|
||||
{
|
||||
rename($fPath, $fPath . '-archive[' . date('Y-m-d') . ']');
|
||||
}
|
||||
fclose(fopen($fPath, 'a+'));
|
||||
$this->fHandle = fopen($fPath, 'r+');
|
||||
ftruncate($this->fHandle, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->fHandle = fopen($fPath, 'a+');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->fHandle = fopen($fPath, 'a+');
|
||||
}
|
||||
}
|
||||
|
||||
$this->time = microtime(true);
|
||||
$this->aLog = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает новый объект-логгер
|
||||
*
|
||||
* @static
|
||||
* @param $fPath - путь к файлу куда необходимо записать лог
|
||||
*/
|
||||
public static function Create($fPath)
|
||||
{
|
||||
if (isset(self::$loggers[$fPath]))
|
||||
{
|
||||
return self::$loggers[$fPath];
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::$loggers[$fPath] = new self($fPath);
|
||||
}
|
||||
}
|
||||
|
||||
private function Add($str, $msgType)
|
||||
{
|
||||
$this->aLog[] = "--- {$msgType}: " . date('Y-m-d H:i:s') . " ---" . PHP_EOL .
|
||||
$str . PHP_EOL .
|
||||
"------------------------------------" . PHP_EOL;
|
||||
$this->Flush();
|
||||
}
|
||||
|
||||
public function Error($str)
|
||||
{
|
||||
$this->Add($str, 'Error');
|
||||
}
|
||||
|
||||
public function Ok($str)
|
||||
{
|
||||
$this->Add($str, 'Ok');
|
||||
}
|
||||
|
||||
public function Message($str)
|
||||
{
|
||||
$this->Add($str, 'Message');
|
||||
}
|
||||
|
||||
public function Flush()
|
||||
{
|
||||
$sLog = '';
|
||||
if (sizeof($this->aLog))
|
||||
{
|
||||
$sLog = implode(self::LOG_SEPARATOR, $this->aLog);
|
||||
}
|
||||
|
||||
if (empty($sLog))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$sLog .= self::LOG_SEPARATOR;
|
||||
fputs($this->fHandle, $sLog);
|
||||
$this->aLog = array();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->Flush();
|
||||
fclose($this->fHandle);
|
||||
}
|
||||
};
|
||||
?>
|
||||
201
lib/FileSys.php
Executable file
201
lib/FileSys.php
Executable file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс для работы с каталогами и файлами в системе
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class FileSys
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static function FilenameSecurity($str)
|
||||
{
|
||||
$bad = array(
|
||||
"../",
|
||||
"./",
|
||||
"<!--",
|
||||
"-->",
|
||||
"<",
|
||||
">",
|
||||
"'",
|
||||
'"',
|
||||
'&',
|
||||
'$',
|
||||
'#',
|
||||
'{',
|
||||
'}',
|
||||
'[',
|
||||
']',
|
||||
'=',
|
||||
';',
|
||||
'?',
|
||||
"%20",
|
||||
"%22",
|
||||
"%3c", // <
|
||||
"%253c", // <
|
||||
"%3e", // >
|
||||
"%0e", // >
|
||||
"%28", // (
|
||||
"%29", // )
|
||||
"%2528", // (
|
||||
"%26", // &
|
||||
"%24", // $
|
||||
"%3f", // ?
|
||||
"%3b", // ;
|
||||
"%3d" // =
|
||||
);
|
||||
|
||||
return stripslashes(str_replace($bad, '', $str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет каталог с его содержимым
|
||||
*/
|
||||
public static function DeleteDir($directory)
|
||||
{
|
||||
$dir = opendir($directory);
|
||||
while (($file = readdir($dir)))
|
||||
{
|
||||
if (is_file($directory . '/' . $file))
|
||||
{
|
||||
unlink($directory . '/' . $file);
|
||||
}
|
||||
elseif (is_dir($directory . '/' . $file) && ($file != '.') && ($file != '..'))
|
||||
{
|
||||
self::DeleteDir($directory . '/' . $file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
return rmdir($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать каталоги по указанному пути
|
||||
*/
|
||||
public static function MakeDir($path)
|
||||
{
|
||||
if (is_dir($path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$path = explode('/', $path);
|
||||
if (count($path) == 1)
|
||||
{
|
||||
$path = explode('\\', $path[0]);
|
||||
}
|
||||
|
||||
$d = '';
|
||||
foreach ($path as $dir)
|
||||
{
|
||||
$d .= $dir . '/';
|
||||
if (!in_array($d, array('/', '/home/')) && !is_dir($d))
|
||||
{
|
||||
mkdir($d, 0777);
|
||||
@chmod($d, 0777);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cоздать и записать содержимое в файл по указанному пути, если каталоги указанные в пути не созданы, то их создадут
|
||||
*/
|
||||
public static function WriteFile($file, $data, $flgAppend = false)
|
||||
{
|
||||
$file = self::FilenameSecurity($file);
|
||||
if (!$flgAppend)
|
||||
{
|
||||
if (file_exists($file))
|
||||
{
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
self::MakeDir(dirname($file));
|
||||
fclose(fopen($file, 'a+b'));
|
||||
$f = fopen($file, $flgAppend ? 'a+b' : 'r+b');
|
||||
$ret = fwrite($f, $data);
|
||||
fclose($f);
|
||||
@chmod($file, 0777);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает файл по переданному пути
|
||||
*/
|
||||
public static function ReadFile($file)
|
||||
{
|
||||
$file = self::FilenameSecurity(trim($file));
|
||||
|
||||
if (!strlen(trim($file)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$b = false;
|
||||
if (strstr($file, 'http://') == $file)
|
||||
{
|
||||
$f = fopen($file, 'rb');
|
||||
while (!feof($f))
|
||||
{
|
||||
$b .= fread($f, 1024);
|
||||
}
|
||||
}
|
||||
elseif (file_exists($file) && is_readable($file))
|
||||
{
|
||||
$f = fopen($file, 'rb');
|
||||
$size = filesize($file);
|
||||
$b = ($size == 0) ? "" : fread($f, $size);
|
||||
}
|
||||
|
||||
if (isset($f) && $f)
|
||||
{
|
||||
fclose($f);
|
||||
}
|
||||
|
||||
return $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает список файлов каталога со всеми вложенными каталогами
|
||||
*
|
||||
* @param string $dir
|
||||
* @return array
|
||||
*/
|
||||
public static function ReadList($dir)
|
||||
{
|
||||
if (!is_readable($dir))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$list = array();
|
||||
$dir = in_array(substr($dir, -1, 1), array('/', '\\')) ? $dir : "$dir/";
|
||||
$hDir = opendir($dir);
|
||||
while(($f = readdir($hDir)) !== false)
|
||||
{
|
||||
if ($f != '.' && $f != '..')
|
||||
{
|
||||
$path = $dir . $f;
|
||||
$list[$f] = is_dir($path) ? self::ReadList($path) : $path;
|
||||
}
|
||||
}
|
||||
closedir($hDir);
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает размер файла в виде: Kb Mb Gb
|
||||
*/
|
||||
public static function Size($file)
|
||||
{
|
||||
$size = sprintf("%u", filesize($file));
|
||||
$filesizename = array(" Bytes", " Kb", " Mb", " Gb", " Tb", " Pb", " Eb", " Zb", " Yb");
|
||||
return $size ? round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . $filesizename[$i] : '0 Bytes';
|
||||
}
|
||||
};
|
||||
?>
|
||||
93
lib/FlashParam.php
Executable file
93
lib/FlashParam.php
Executable file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Параметры запоминаемые на 1 (N) переходов
|
||||
*
|
||||
* Хорошо подгодят для сообщений вида "Успешно добавлено" которое при обновлении страницы пропадет
|
||||
*
|
||||
* @author Zmi
|
||||
* @author Guul
|
||||
*/
|
||||
class FlashParam
|
||||
{
|
||||
const SESSION_PAR = '__engineSessionFlashParamSector__';
|
||||
|
||||
// Флаг проводилась ли очистка параметров
|
||||
private static $cleanParams = false;
|
||||
private $opened = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->opened = true;
|
||||
try
|
||||
{
|
||||
@session_start();
|
||||
}
|
||||
catch(ErrorExpression $e)
|
||||
{
|
||||
$this->opened = false;
|
||||
}
|
||||
if (!self::$cleanParams)
|
||||
{
|
||||
self::$cleanParams = true;
|
||||
$this->AutoClean();
|
||||
}
|
||||
}
|
||||
|
||||
public function Get($name)
|
||||
{
|
||||
$ret = false;
|
||||
if (isset($_SESSION[self::SESSION_PAR]))
|
||||
{
|
||||
if (isset($_SESSION[self::SESSION_PAR][$name]))
|
||||
{
|
||||
$ret = $_SESSION[self::SESSION_PAR][$name]['value'];
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function Set($name, $value, $countSessionRemember = 1)
|
||||
{
|
||||
$elem = array
|
||||
(
|
||||
'value' => $value,
|
||||
'countSessionPass' => $countSessionRemember,
|
||||
'time' => time()
|
||||
);
|
||||
if (!isset($_SESSION[self::SESSION_PAR]))
|
||||
{
|
||||
$_SESSION[self::SESSION_PAR] = array();
|
||||
}
|
||||
$_SESSION[self::SESSION_PAR][$name] = $elem;
|
||||
}
|
||||
|
||||
private function AutoClean()
|
||||
{
|
||||
if (!isset($_SESSION[self::SESSION_PAR]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach ($_SESSION[self::SESSION_PAR] as $k => $v)
|
||||
{
|
||||
if ($v['countSessionPass'] < 1)
|
||||
{
|
||||
unset($_SESSION[self::SESSION_PAR][$k]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$v['countSessionPass'] = $v['countSessionPass'] - 1;
|
||||
$_SESSION[self::SESSION_PAR][$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->opened)
|
||||
{
|
||||
session_write_close();
|
||||
}
|
||||
}
|
||||
};
|
||||
?>
|
||||
80
lib/HtmlValidate.php
Executable file
80
lib/HtmlValidate.php
Executable file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Валидатор html кода
|
||||
*
|
||||
* Сливает head-ы читит плохие символы и немножко валидирует код
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class HtmlValidate
|
||||
{
|
||||
private $html;
|
||||
|
||||
public function __construct($html)
|
||||
{
|
||||
$this->html = $this->HeadBodyMerge($html);
|
||||
}
|
||||
|
||||
public function Get()
|
||||
{
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Убирает вложенные head-ы
|
||||
*/
|
||||
private function DeleteInnerHtml($html)
|
||||
{
|
||||
preg_match('~<head(.*?)>~is', $html, $m);
|
||||
|
||||
if (!isset($m[0]))
|
||||
{
|
||||
return $html;
|
||||
}
|
||||
|
||||
$headAttrs = isset($m[1]) ? $m[1] : ''; // Атрибуты главного head-а если были
|
||||
$html = $m[0] === '<head>' ? $html : str_ireplace($m[0], '<head>', $html); // Заменяем первый head с атрибутами на обычный что бы проще было искать
|
||||
|
||||
do
|
||||
{
|
||||
$open = stripos($html, '<head>') + strlen('<head>');
|
||||
$end = stripos($html, '</head>', $open); // Ищем где он закрывается
|
||||
$head = substr($html, $open, $end - $open + strlen('</head>')); // Берём этот подконтент
|
||||
|
||||
// Есть ли в этом контенте подхеад?
|
||||
$open2 = strripos($head, '<head>');
|
||||
$end2 = $open2 + strlen('<head>');
|
||||
if ($open2 !== false) // Если есть то он уже в хеаде, потому убираем его
|
||||
{
|
||||
$headWithoutSub = substr($head, 0, $open2) . // От начала head до того где в нём нашли вложеный head
|
||||
substr($head, $end2, strlen($head) - $end2 - strlen('</head>'));
|
||||
$html = str_ireplace($head, $headWithoutSub, $html);
|
||||
}
|
||||
} while ($open2 !== false); // Если в контенте все еще есть открывающийся head
|
||||
|
||||
return _StrReplaceFirst('<head>', "<head{$headAttrs}>", $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сливает несколько разделов head в 1
|
||||
*/
|
||||
private function HeadBodyMerge($html)
|
||||
{
|
||||
$html = $this->DeleteInnerHtml($html);
|
||||
|
||||
preg_match_all('~<head(.*?)>(.*?)</head>~is', $html, $m);
|
||||
// Если <head>...</head> больше чем 1 тогда имеет смысл их объединять
|
||||
if (count($m[0]) > 1)
|
||||
{
|
||||
// Собираем общий head
|
||||
$head = '<head' . $m[1][0] . '>' . implode('', array_filter(array_unique($m[2]))) . '</head>';
|
||||
|
||||
// Заменяем все <head>...</head> на 1 слитый
|
||||
$html = str_replace($m[0][0], $head, $html);
|
||||
$html = str_replace(array_splice($m[0], 1), '', $html);
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
};
|
||||
?>
|
||||
519
lib/InputClean.php
Executable file
519
lib/InputClean.php
Executable file
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс очистки входных данных взят из CodeIgniter
|
||||
*/
|
||||
class InputClean
|
||||
{
|
||||
private $use_xss_clean = true;
|
||||
private $xss_hash = '';
|
||||
private $charset = 'UTF-8';
|
||||
|
||||
private $never_allowed_str = array(
|
||||
'document.cookie' => '[removed]',
|
||||
'document.write' => '[removed]',
|
||||
'.parentNode' => '[removed]',
|
||||
'.innerHTML' => '[removed]',
|
||||
'window.location' => '[removed]',
|
||||
'-moz-binding' => '[removed]',
|
||||
'<!--' => '<!--',
|
||||
'-->' => '-->',
|
||||
'<![CDATA[' => '<![CDATA['
|
||||
);
|
||||
|
||||
private $never_allowed_regex = array(
|
||||
"javascript\s*:" => '[removed]',
|
||||
"expression\s*(\(|&\#40;)" => '[removed]',
|
||||
"vbscript\s*:" => '[removed]',
|
||||
"Redirect\s+302" => '[removed]'
|
||||
);
|
||||
|
||||
public function __construct($charset)
|
||||
{
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
public function _clean_input_data($str)
|
||||
{
|
||||
if (is_array($str))
|
||||
{
|
||||
$new_array = array();
|
||||
foreach ($str as $key => $val)
|
||||
{
|
||||
$new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
|
||||
}
|
||||
return $new_array;
|
||||
}
|
||||
|
||||
// We strip slashes if magic quotes is on to keep things consistent
|
||||
if (get_magic_quotes_gpc())
|
||||
{
|
||||
$str = stripslashes($str);
|
||||
}
|
||||
|
||||
// Should we filter the input data?
|
||||
if ($this->use_xss_clean === TRUE)
|
||||
{
|
||||
$str = $this->xss_clean($str);
|
||||
}
|
||||
|
||||
// Standardize newlines
|
||||
if (strpos($str, "\r") !== FALSE)
|
||||
{
|
||||
$str = str_replace(array("\r\n", "\r"), "\n", $str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function _clean_input_keys($str)
|
||||
{
|
||||
if (!preg_match("/^[a-z0-9:_\/-]+$/i", $str))
|
||||
{
|
||||
exit('Disallowed Key Characters.');
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
public function filename_security($str)
|
||||
{
|
||||
$bad = array(
|
||||
"../",
|
||||
"./",
|
||||
"<!--",
|
||||
"-->",
|
||||
"<",
|
||||
">",
|
||||
"'",
|
||||
'"',
|
||||
'&',
|
||||
'$',
|
||||
'#',
|
||||
'{',
|
||||
'}',
|
||||
'[',
|
||||
']',
|
||||
'=',
|
||||
';',
|
||||
'?',
|
||||
"%20",
|
||||
"%22",
|
||||
"%3c", // <
|
||||
"%253c", // <
|
||||
"%3e", // >
|
||||
"%0e", // >
|
||||
"%28", // (
|
||||
"%29", // )
|
||||
"%2528", // (
|
||||
"%26", // &
|
||||
"%24", // $
|
||||
"%3f", // ?
|
||||
"%3b", // ;
|
||||
"%3d" // =
|
||||
);
|
||||
|
||||
return stripslashes(str_replace($bad, '', $str));
|
||||
}
|
||||
|
||||
public function xss_clean($str, $is_image = false)
|
||||
{
|
||||
/*
|
||||
* Is the string an array?
|
||||
*
|
||||
*/
|
||||
if (is_array($str))
|
||||
{
|
||||
while (list($key) = each($str))
|
||||
{
|
||||
$str[$key] = $this->xss_clean($str[$key]);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove Invisible Characters
|
||||
*/
|
||||
$str = $this->_remove_invisible_characters($str);
|
||||
|
||||
/*
|
||||
* Protect GET variables in URLs
|
||||
*/
|
||||
|
||||
// 901119URL5918AMP18930PROTECT8198
|
||||
|
||||
$str = preg_replace('|\&([a-z\_0-9]+)\=([a-z\_0-9]+)|i', $this->xss_hash()."\\1=\\2", $str);
|
||||
|
||||
/*
|
||||
* Validate standard character entities
|
||||
*
|
||||
* Add a semicolon if missing. We do this to enable
|
||||
* the conversion of entities to ASCII later.
|
||||
*
|
||||
*/
|
||||
$str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str);
|
||||
|
||||
/*
|
||||
* Validate UTF16 two byte encoding (x00)
|
||||
*
|
||||
* Just as above, adds a semicolon if missing.
|
||||
*
|
||||
*/
|
||||
$str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str);
|
||||
|
||||
/*
|
||||
* Un-Protect GET variables in URLs
|
||||
*/
|
||||
$str = str_replace($this->xss_hash(), '&', $str);
|
||||
|
||||
/*
|
||||
* URL Decode
|
||||
*
|
||||
* Just in case stuff like this is submitted:
|
||||
*
|
||||
* <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
|
||||
*
|
||||
* Note: Use rawurldecode() so it does not remove plus signs
|
||||
*
|
||||
*/
|
||||
$str = rawurldecode($str);
|
||||
|
||||
/*
|
||||
* Convert character entities to ASCII
|
||||
*
|
||||
* This permits our tests below to work reliably.
|
||||
* We only convert entities that are within tags since
|
||||
* these are the ones that will pose security problems.
|
||||
*
|
||||
*/
|
||||
|
||||
$str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
|
||||
|
||||
$str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_html_entity_decode_callback'), $str);
|
||||
|
||||
/*
|
||||
* Remove Invisible Characters Again!
|
||||
*/
|
||||
$str = $this->_remove_invisible_characters($str);
|
||||
|
||||
/*
|
||||
* Convert all tabs to spaces
|
||||
*
|
||||
* This prevents strings like this: ja vascript
|
||||
* NOTE: we deal with spaces between characters later.
|
||||
* NOTE: preg_replace was found to be amazingly slow here on large blocks of data,
|
||||
* so we use str_replace.
|
||||
*
|
||||
*/
|
||||
|
||||
if (strpos($str, "\t") !== FALSE)
|
||||
{
|
||||
$str = str_replace("\t", ' ', $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Capture converted string for later comparison
|
||||
*/
|
||||
$converted_string = $str;
|
||||
|
||||
/*
|
||||
* Not Allowed Under Any Conditions
|
||||
*/
|
||||
|
||||
foreach ($this->never_allowed_str as $key => $val)
|
||||
{
|
||||
$str = str_replace($key, $val, $str);
|
||||
}
|
||||
|
||||
foreach ($this->never_allowed_regex as $key => $val)
|
||||
{
|
||||
$str = preg_replace("#".$key."#i", $val, $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Makes PHP tags safe
|
||||
*
|
||||
* Note: XML tags are inadvertently replaced too:
|
||||
*
|
||||
* <?xml
|
||||
*
|
||||
* But it doesn't seem to pose a problem.
|
||||
*
|
||||
*/
|
||||
if ($is_image === TRUE)
|
||||
{
|
||||
// Images have a tendency to have the PHP short opening and closing tags every so often
|
||||
// so we skip those and only do the long opening tags.
|
||||
$str = str_replace(array('<?php', '<?PHP'), array('<?php', '<?PHP'), $str);
|
||||
}
|
||||
else
|
||||
{
|
||||
$str = str_replace(array('<?php', '<?PHP', '<?', '?'.'>'), array('<?php', '<?PHP', '<?', '?>'), $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compact any exploded words
|
||||
*
|
||||
* This corrects words like: j a v a s c r i p t
|
||||
* These words are compacted back to their correct state.
|
||||
*
|
||||
*/
|
||||
$words = array('javascript', 'expression', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window');
|
||||
foreach ($words as $word)
|
||||
{
|
||||
$temp = '';
|
||||
|
||||
for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++)
|
||||
{
|
||||
$temp .= substr($word, $i, 1)."\s*";
|
||||
}
|
||||
|
||||
// We only want to do this when it is followed by a non-word character
|
||||
// That way valid stuff like "dealer to" does not become "dealerto"
|
||||
$str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove disallowed Javascript in links or img tags
|
||||
* We used to do some version comparisons and use of stripos for PHP5, but it is dog slow compared
|
||||
* to these simplified non-capturing preg_match(), especially if the pattern exists in the string
|
||||
*/
|
||||
do
|
||||
{
|
||||
$original = $str;
|
||||
|
||||
if (preg_match("/<a/i", $str))
|
||||
{
|
||||
$str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str);
|
||||
}
|
||||
|
||||
if (preg_match("/<img/i", $str))
|
||||
{
|
||||
$str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str);
|
||||
}
|
||||
|
||||
if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
|
||||
{
|
||||
$str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str);
|
||||
}
|
||||
}
|
||||
while($original != $str);
|
||||
|
||||
unset($original);
|
||||
|
||||
/*
|
||||
* Remove JavaScript Event Handlers
|
||||
*
|
||||
* Note: This code is a little blunt. It removes
|
||||
* the event handler and anything up to the closing >,
|
||||
* but it's unlikely to be a problem.
|
||||
*
|
||||
*/
|
||||
$event_handlers = array('[^a-z_\-]on\w*','xmlns');
|
||||
|
||||
if ($is_image === TRUE)
|
||||
{
|
||||
/*
|
||||
* Adobe Photoshop puts XML metadata into JFIF images, including namespacing,
|
||||
* so we have to allow this for images. -Paul
|
||||
*/
|
||||
unset($event_handlers[array_search('xmlns', $event_handlers)]);
|
||||
}
|
||||
|
||||
$str = preg_replace("#<([^><]+?)(".implode('|', $event_handlers).")(\s*=\s*[^><]*)([><]*)#i", "<\\1\\4", $str);
|
||||
|
||||
/*
|
||||
* Sanitize naughty HTML elements
|
||||
*
|
||||
* If a tag containing any of the words in the list
|
||||
* below is found, the tag gets converted to entities.
|
||||
*
|
||||
* So this: <blink>
|
||||
* Becomes: <blink>
|
||||
*
|
||||
*/
|
||||
$naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss';
|
||||
$str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
|
||||
|
||||
/*
|
||||
* Sanitize naughty scripting elements
|
||||
*
|
||||
* Similar to above, only instead of looking for
|
||||
* tags it looks for PHP and JavaScript commands
|
||||
* that are disallowed. Rather than removing the
|
||||
* code, it simply converts the parenthesis to entities
|
||||
* rendering the code un-executable.
|
||||
*
|
||||
* For example: eval('some code')
|
||||
* Becomes: eval('some code')
|
||||
*
|
||||
*/
|
||||
$str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $str);
|
||||
|
||||
/*
|
||||
* Final clean up
|
||||
*
|
||||
* This adds a bit of extra precaution in case
|
||||
* something got through the above filters
|
||||
*
|
||||
*/
|
||||
foreach ($this->never_allowed_str as $key => $val)
|
||||
{
|
||||
$str = str_replace($key, $val, $str);
|
||||
}
|
||||
|
||||
foreach ($this->never_allowed_regex as $key => $val)
|
||||
{
|
||||
$str = preg_replace("#".$key."#i", $val, $str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Images are Handled in a Special Way
|
||||
* - Essentially, we want to know that after all of the character conversion is done whether
|
||||
* any unwanted, likely XSS, code was found. If not, we return TRUE, as the image is clean.
|
||||
* However, if the string post-conversion does not matched the string post-removal of XSS,
|
||||
* then it fails, as there was unwanted XSS code found and removed/changed during processing.
|
||||
*/
|
||||
|
||||
if ($is_image === TRUE)
|
||||
{
|
||||
if ($str == $converted_string)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function xss_hash()
|
||||
{
|
||||
if ($this->xss_hash == '')
|
||||
{
|
||||
if (phpversion() >= 4.2)
|
||||
mt_srand();
|
||||
else
|
||||
mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
|
||||
|
||||
$this->xss_hash = md5(time() + mt_rand(0, 1999999999));
|
||||
}
|
||||
|
||||
return $this->xss_hash;
|
||||
}
|
||||
|
||||
private function _remove_invisible_characters($str)
|
||||
{
|
||||
static $non_displayables;
|
||||
|
||||
if (!isset($non_displayables))
|
||||
{
|
||||
// every control character except newline (dec 10), carriage return (dec 13), and horizontal tab (dec 09),
|
||||
$non_displayables = array(
|
||||
'/%0[0-8bcef]/', // url encoded 00-08, 11, 12, 14, 15
|
||||
'/%1[0-9a-f]/', // url encoded 16-31
|
||||
'/[\x00-\x08]/', // 00-08
|
||||
'/\x0b/', '/\x0c/', // 11, 12
|
||||
'/[\x0e-\x1f]/' // 14-31
|
||||
);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
$cleaned = $str;
|
||||
$str = preg_replace($non_displayables, '', $str);
|
||||
}
|
||||
while ($cleaned != $str);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function _compact_exploded_words($matches)
|
||||
{
|
||||
return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
|
||||
}
|
||||
|
||||
private function _sanitize_naughty_html($matches)
|
||||
{
|
||||
// encode opening brace
|
||||
$str = '<'.$matches[1].$matches[2].$matches[3];
|
||||
|
||||
// encode captured opening or closing brace to prevent recursive vectors
|
||||
$str .= str_replace(array('>', '<'), array('>', '<'), $matches[4]);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function _js_link_removal($match)
|
||||
{
|
||||
$attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
|
||||
return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
|
||||
}
|
||||
|
||||
private function _js_img_removal($match)
|
||||
{
|
||||
$attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
|
||||
return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
|
||||
}
|
||||
|
||||
private function _convert_attribute($match)
|
||||
{
|
||||
return str_replace(array('>', '<', '\\'), array('>', '<', '\\\\'), $match[0]);
|
||||
}
|
||||
|
||||
private function _html_entity_decode_callback($match)
|
||||
{
|
||||
return $this->_html_entity_decode($match[0], strtoupper($this->charset));
|
||||
}
|
||||
|
||||
private function _html_entity_decode($str, $charset='UTF-8')
|
||||
{
|
||||
if (stristr($str, '&') === FALSE) return $str;
|
||||
|
||||
// The reason we are not using html_entity_decode() by itself is because
|
||||
// while it is not technically correct to leave out the semicolon
|
||||
// at the end of an entity most browsers will still interpret the entity
|
||||
// correctly. html_entity_decode() does not convert entities without
|
||||
// semicolons, so we are left with our own little solution here. Bummer.
|
||||
|
||||
if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR version_compare(phpversion(), '5.0.0', '>=')))
|
||||
{
|
||||
$str = html_entity_decode($str, ENT_COMPAT, $charset);
|
||||
$str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str);
|
||||
return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str);
|
||||
}
|
||||
|
||||
// Numeric Entities
|
||||
$str = preg_replace('~&#x(0*[0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str);
|
||||
$str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str);
|
||||
|
||||
// Literal Entities - Slightly slow so we do another check
|
||||
if (stristr($str, '&') === FALSE)
|
||||
{
|
||||
$str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES)));
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function _filter_attributes($str)
|
||||
{
|
||||
$out = '';
|
||||
|
||||
if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
|
||||
{
|
||||
foreach ($matches[0] as $match)
|
||||
{
|
||||
$out .= preg_replace("#/\*.*?\*/#s", '', $match);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
};
|
||||
?>
|
||||
171
lib/Php.php
Executable file
171
lib/Php.php
Executable file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Класс работы с пхп
|
||||
*
|
||||
* @author Zmi
|
||||
*/
|
||||
class Php
|
||||
{
|
||||
// Массив с перечнем кодов ответов от сервера
|
||||
private static $codes = array
|
||||
(
|
||||
'200' => 'OK',
|
||||
'201' => 'Created',
|
||||
'202' => 'Accepted',
|
||||
'203' => 'Non-Authoritative Information',
|
||||
'204' => 'No Content',
|
||||
'205' => 'Reset Content',
|
||||
'206' => 'Partial Content',
|
||||
|
||||
'300' => 'Multiple Choices',
|
||||
'301' => 'Moved Permanently',
|
||||
'302' => 'Found',
|
||||
'304' => 'Not Modified',
|
||||
'305' => 'Use Proxy',
|
||||
'307' => 'Temporary Redirect',
|
||||
|
||||
'400' => 'Bad Request',
|
||||
'401' => 'Unauthorized',
|
||||
'403' => 'Forbidden',
|
||||
'404' => 'Not Found',
|
||||
'405' => 'Method Not Allowed',
|
||||
'406' => 'Not Acceptable',
|
||||
'407' => 'Proxy Authentication Required',
|
||||
'408' => 'Request Timeout',
|
||||
'409' => 'Conflict',
|
||||
'410' => 'Gone',
|
||||
'411' => 'Length Required',
|
||||
'412' => 'Precondition Failed',
|
||||
'413' => 'Request Entity Too Large',
|
||||
'414' => 'Request-URI Too Long',
|
||||
'415' => 'Unsupported Media Type',
|
||||
'416' => 'Requested Range Not Satisfiable',
|
||||
'417' => 'Expectation Failed',
|
||||
|
||||
'500' => 'Internal Server Error',
|
||||
'501' => 'Not Implemented',
|
||||
'502' => 'Bad Gateway',
|
||||
'503' => 'Service Unavailable',
|
||||
'504' => 'Gateway Timeout',
|
||||
'505' => 'HTTP Version Not Supported'
|
||||
);
|
||||
|
||||
/**
|
||||
* Вернет правильный статус для отсылки его по header
|
||||
*/
|
||||
public static function Status($code)
|
||||
{
|
||||
if (empty($code) || !is_numeric($code) || !isset(self::$codes[$code]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$text = self::$codes[$code];
|
||||
$serverProtocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : false;
|
||||
|
||||
if (substr(php_sapi_name(), 0, 3) == 'cgi')
|
||||
{
|
||||
$ret = "Status: {$code} {$text}";
|
||||
}
|
||||
elseif ($serverProtocol == 'HTTP/1.1' or $serverProtocol == 'HTTP/1.0')
|
||||
{
|
||||
$ret = $serverProtocol . " {$code} {$text}";
|
||||
}
|
||||
else
|
||||
{
|
||||
$ret = "HTTP/1.1 {$code} {$text}";
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function StripslashesGpc(&$value)
|
||||
{
|
||||
$value = stripslashes($value);
|
||||
}
|
||||
|
||||
// Нормализация переменных в массивах при включенном magic_quotes (так же чистит от html)
|
||||
private static function NormalizeArrays()
|
||||
{
|
||||
if (get_magic_quotes_gpc())
|
||||
{
|
||||
if (is_array($_FILES))
|
||||
{
|
||||
foreach ($_FILES AS $key=>$val)
|
||||
{
|
||||
$_FILES[$key]['tmp_name'] = str_replace('\\\\', '\\', $val['tmp_name']);
|
||||
}
|
||||
}
|
||||
|
||||
array_walk_recursive($_GET, array(__CLASS__, 'StripslashesGpc'));
|
||||
array_walk_recursive($_POST, array(__CLASS__, 'StripslashesGpc'));
|
||||
array_walk_recursive($_COOKIE, array(__CLASS__, 'StripslashesGpc'));
|
||||
array_walk_recursive($_REQUEST, array(__CLASS__, 'StripslashesGpc'));
|
||||
|
||||
set_magic_quotes_runtime(0);
|
||||
}
|
||||
}
|
||||
|
||||
private function DebugErrorHook()
|
||||
{
|
||||
global $g_config;
|
||||
static $errorListner = NULL;
|
||||
|
||||
$path = dirname(__FILE__) . '/Debug/ErrorHook/';
|
||||
require_once $path . 'Listener.php';
|
||||
require_once $path . 'Catcher.php';
|
||||
require_once $path . 'INotifier.php';
|
||||
require_once $path . 'Util.php';
|
||||
require_once $path . 'TextNotifier.php';
|
||||
require_once $path . 'MailNotifier.php';
|
||||
require_once $path . 'RemoveDupsWrapper.php';
|
||||
require_once $path . 'my/MyDebug_ErrorHook_TextNotifier.php';
|
||||
|
||||
$cfgLog = $g_config['logErrors'];
|
||||
|
||||
FileSys::MakeDir(dirname($cfgLog['repeatTmp']));
|
||||
|
||||
$errorListner = new Debug_ErrorHook_Listener();
|
||||
$errorListner->addNotifier(new MyDebug_ErrorHook_TextNotifier(MyDebug_ErrorHook_TextNotifier::LOG_ALL));
|
||||
if (!empty($cfgLog['email']))
|
||||
{
|
||||
$errorListner->addNotifier(
|
||||
new Debug_ErrorHook_RemoveDupsWrapper(
|
||||
new Debug_ErrorHook_MailNotifier($cfgLog['email'], Debug_ErrorHook_TextNotifier::LOG_ALL),
|
||||
$cfgLog['repeatTmp'] . "/email/",
|
||||
$cfgLog['emailTimeRepeat']
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $g_config;
|
||||
|
||||
// Настраиваем php
|
||||
foreach ($g_config['phpIni'] as $k => $v)
|
||||
{
|
||||
ini_set($k, $v);
|
||||
}
|
||||
|
||||
self::NormalizeArrays();
|
||||
|
||||
setlocale(LC_ALL, "en_US.UTF-8", "English");
|
||||
date_default_timezone_set('Europe/London');
|
||||
|
||||
if (function_exists("mb_internal_encoding") && function_exists("mb_regex_encoding"))
|
||||
{
|
||||
mb_internal_encoding($g_config['charset']);
|
||||
mb_regex_encoding($g_config['charset']);
|
||||
}
|
||||
|
||||
if ($g_config['useDebugErrorHook'])
|
||||
{
|
||||
// Подключаем слежку за ошибками
|
||||
$this->DebugErrorHook();
|
||||
}
|
||||
}
|
||||
};
|
||||
?>
|
||||
107
lib/Uploader.php
Executable file
107
lib/Uploader.php
Executable file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
require_once BASEPATH . 'lib/CI_Upload.php';
|
||||
|
||||
|
||||
/**
|
||||
* Класс для загрузки файлов и изображений
|
||||
*
|
||||
* @author Zmi and Guul
|
||||
*/
|
||||
class Uploader extends CI_Upload
|
||||
{
|
||||
const FORM_LOAD = 'enctype="multipart/form-data" method="post"';
|
||||
|
||||
protected $thumbPaths = array();
|
||||
|
||||
|
||||
public function Upload($field, $config = array())
|
||||
{
|
||||
global $g_config;
|
||||
|
||||
$cfg = $g_config['uploader']['default_config'];
|
||||
foreach($config as $k => $v)
|
||||
{
|
||||
$cfg[$k] = $v;
|
||||
}
|
||||
|
||||
FileSys::MakeDir($cfg['upload_path']);
|
||||
$this->initialize($cfg);
|
||||
|
||||
$ret = $this->do_upload($field);
|
||||
$inf = $this->data();
|
||||
|
||||
// Уменьшаем
|
||||
if ($ret && $inf['is_image'] && ($cfg['resize_down_width'] || $cfg['resize_down_height']))
|
||||
{
|
||||
$w = $cfg['resize_down_width'];
|
||||
$h = $cfg['resize_down_height'];
|
||||
$w = $w ? $w : null;
|
||||
$h = $h ? $h : null;
|
||||
|
||||
if (($w && $inf['image_width'] > $w) ||
|
||||
($h && $inf['image_height'] > $h))
|
||||
{
|
||||
$wImg = WideImage::load($inf['full_path']);
|
||||
$wImg = $wImg->resizeDown($w, $h, 'inside', 'down');
|
||||
|
||||
if ($inf['image_type'] == "jpeg")
|
||||
{
|
||||
$wImg->saveToFile($inf['full_path'], 82); // в разы уменьшаем размер изображения / + без этого хака jpg изображение может не сохранится
|
||||
}
|
||||
else
|
||||
{
|
||||
$wImg->saveToFile($inf['full_path']);
|
||||
}
|
||||
$this->set_image_properties($this->upload_path.$this->file_name); // Обновляем данные аплоадера
|
||||
$inf = $this->data();
|
||||
}
|
||||
}
|
||||
|
||||
// Генерируем превьюшки
|
||||
if ($ret && $inf['is_image'] && is_array($cfg['thumbs']))
|
||||
{
|
||||
foreach ($cfg['thumbs'] as $v)
|
||||
{
|
||||
$newDir = "";
|
||||
if (!isset($v['path']))
|
||||
{
|
||||
$newDir = $cfg['upload_path'] . $v['width'] . '_' . $v['height'] . '/';
|
||||
}
|
||||
else
|
||||
{
|
||||
$newDir = $v['path'];
|
||||
}
|
||||
|
||||
FileSys::MakeDir($newDir);
|
||||
$image = WideImage::load($inf['full_path']);
|
||||
$image->resize($v['width'], $v['height'])->saveToFile($newDir . $inf['file_name']);
|
||||
|
||||
$this->thumbPaths[] = $newDir . $inf['file_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function HasUpload($field)
|
||||
{
|
||||
return isset($_FILES[$field]) && isset($_FILES[$field]["tmp_name"]) && $_FILES[$field]["tmp_name"];
|
||||
}
|
||||
|
||||
public function Errors()
|
||||
{
|
||||
return $this->error_msg;
|
||||
}
|
||||
|
||||
public function GetInf($par = NULL)
|
||||
{
|
||||
$inf = $this->data();
|
||||
if (is_null($par))
|
||||
{
|
||||
return $inf;
|
||||
}
|
||||
return isset($inf[$par]) ? $inf[$par] : NULL;
|
||||
}
|
||||
};
|
||||
?>
|
||||
185
lib/ckeditor4/CHANGES.md
Executable file
185
lib/ckeditor4/CHANGES.md
Executable file
@@ -0,0 +1,185 @@
|
||||
CKEditor 4 Changelog
|
||||
====================
|
||||
|
||||
## CKEditor 4.2
|
||||
|
||||
**Important Notes:**
|
||||
|
||||
* Dropped compatibility support for Internet Explorer 7 and Firefox 3.6.
|
||||
|
||||
* Both the Basic and the Standard distribution packages will not contain the new [indentblock](http://ckeditor.com/addon/indentblock) plugin. Because of this the [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) might remove block indentations from existing contents. If you want to prevent this, either [add an appropriate ACF rule to your filter](http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules) or create a custom build based on the Basic/Standard package and add the indentblock plugin in [CKBuilder](http://ckeditor.com/builder).
|
||||
|
||||
Fixed issues:
|
||||
|
||||
* [#10027](http://dev.ckeditor.com/ticket/10027): Separated list and block indentation into two plugins: [indentlist](http://ckeditor.com/addon/indentlist) and [indentblock](http://ckeditor.com/addon/indentblock).
|
||||
* [#8244](http://dev.ckeditor.com/ticket/8244): Use *(Shift+)Tab* to indent and outdent lists.
|
||||
* [#10599](http://dev.ckeditor.com/ticket/10599): [Indent](http://ckeditor.com/addon/indent) plugin is no longer required by the [list](http://ckeditor.com/addon/list) plugin.
|
||||
* [#10281](http://dev.ckeditor.com/ticket/10281): The [jQuery Adapter](http://docs.ckeditor.com/#!/guide/dev_jquery) is now available. Several jQuery-related issues fixed: [#8261](http://dev.ckeditor.com/ticket/8261), [#9077](http://dev.ckeditor.com/ticket/9077), [#8710](http://dev.ckeditor.com/ticket/8710), [#8530](http://dev.ckeditor.com/ticket/8530), [#9019](http://dev.ckeditor.com/ticket/9019), [#6181](http://dev.ckeditor.com/ticket/6181), [#7876](http://dev.ckeditor.com/ticket/7876), [#6906](http://dev.ckeditor.com/ticket/6906).
|
||||
* [#10042](http://dev.ckeditor.com/ticket/10042): Introduced [config.title](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-title) setting to change the human-readable title of the editor.
|
||||
* [#10370](http://dev.ckeditor.com/ticket/10370): Inconsistency in data events between framed and inline editors.
|
||||
* [#9794](http://dev.ckeditor.com/ticket/9794): Added [onChange](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-change) event.
|
||||
* [#9923](http://dev.ckeditor.com/ticket/9923): HiDPI support in the editor UI. HiDPI icons for [Moono skin](http://ckeditor.com/addon/moono) added.
|
||||
* [#8031](http://dev.ckeditor.com/ticket/8031): Handle `required` attributes on `<textarea>` elements — introduced [editor#required](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-required) event.
|
||||
* [#10280](http://dev.ckeditor.com/ticket/10280): Ability to replace `<textarea>` elements with the inline editor.
|
||||
* [#10438](http://dev.ckeditor.com/ticket/10438): [FF, IE] No selection is done on an editable element on executing [setData](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setData).
|
||||
|
||||
## CKEditor 4.1.3
|
||||
|
||||
* Added new translation: Indonesian.
|
||||
* [#10644](http://dev.ckeditor.com/ticket/10644): Fixed critical bug when pasting plain text in Blink based browsers.
|
||||
* [#5189](http://dev.ckeditor.com/ticket/5189): Find/Replace dialog window: rename "Cancel" button to "Close".
|
||||
* [#10562](http://dev.ckeditor.com/ticket/10562): [Housekeeping] Unified CSS gradient filter formats in the Moono skin.
|
||||
* [#10537](http://dev.ckeditor.com/ticket/10537): ACF should register a default rule for [config.shiftEnterMode](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-shiftEnterMode).
|
||||
* [#10610](http://dev.ckeditor.com/ticket/10610): [CKEDITOR.dialog.addIframe](http://docs.ckeditor.com/#!/api/CKEDITOR.dialog-static-method-addIframe) incorrectly sets the iframe size in dialog windows.
|
||||
|
||||
## CKEditor 4.1.2
|
||||
|
||||
* Added new translation: Sinhala.
|
||||
* [#10339](http://dev.ckeditor.com/ticket/10339): Fixed: Error thrown when inserted data totally stripped out after filtering and processing.
|
||||
* [#10298](http://dev.ckeditor.com/ticket/10298): Fixed: Data processor breaks attributes containing protected parts.
|
||||
* [#10367](http://dev.ckeditor.com/ticket/10367): Fixed: `editable#insertText` loses characters when `RegExp` replace controls are being inserted.
|
||||
* [#10165](http://dev.ckeditor.com/ticket/10165): [IE] Access denied error when `document.domain` has been altered.
|
||||
* [#9761](http://dev.ckeditor.com/ticket/9761): Update *Backspace* key state in `keystrokeHandler#blockedKeystrokes` when calling `editor.setReadOnly()`.
|
||||
* [#6504](http://dev.ckeditor.com/ticket/6504): Fixed: Race condition while loading several `config.customConfig` files.
|
||||
* [#10146](http://dev.ckeditor.com/ticket/10146): [Firefox] Empty lines are being removed while `config#enterMode` is `CKEDITOR.ENTER_BR`.
|
||||
* [#10360](http://dev.ckeditor.com/ticket/10360): Fixed: ARIA `role="application"` should not be used for dialogs.
|
||||
* [#10361](http://dev.ckeditor.com/ticket/10361): Fixed: ARIA `role="application"` should not be used for floating panels.
|
||||
* [#10510](http://dev.ckeditor.com/ticket/10510): Introduced unique voice labels to differentiate between different editor instances.
|
||||
* [#9945](http://dev.ckeditor.com/ticket/9945): [iOS] Scrolling not possible on iPad.
|
||||
* [#10389](http://dev.ckeditor.com/ticket/10389): Fixed: Invalid HTML in the "Text and Table" template.
|
||||
|
||||
## CKEditor 4.1.1
|
||||
|
||||
* Added new translation: Albanian.
|
||||
* [#10172](http://dev.ckeditor.com/ticket/10172): Pressing *Delete*/*Backspace* in an empty table cell moves the cursor to the next/previous cell.
|
||||
* [#10219](http://dev.ckeditor.com/ticket/10219): Error thrown when destroying an editor instance in parallel with a mouseup event.
|
||||
* [#10265](http://dev.ckeditor.com/ticket/10265): Wrong loop type in the Filebrowser plugin.
|
||||
* [#10249](http://dev.ckeditor.com/ticket/10249): Wrong undo/redo states at start.
|
||||
* [#10268](http://dev.ckeditor.com/ticket/10268): "Show Blocks" does not recover after switching to source view.
|
||||
* [#9995](http://dev.ckeditor.com/ticket/9995): HTML code in `textarea` should not be modified by the `htmlDataProcessor`.
|
||||
* [#10320](http://dev.ckeditor.com/ticket/10320): Justify plugin should add elements to the ACF based on current Enter mode.
|
||||
* [#10260](http://dev.ckeditor.com/ticket/10260): Fixed: Advanced Content Filter blocks `tabSpaces`. Unified `data-cke-*` attributes filtering.
|
||||
* [#10315](http://dev.ckeditor.com/ticket/10315): [Webkit] Undo manager should not record snapshots after a filling character was added/removed.
|
||||
* [#10291](http://dev.ckeditor.com/ticket/10291): [Webkit] Space after a filling character should be secured.
|
||||
* [#10330](http://dev.ckeditor.com/ticket/10330): [Webkit] The filling character is not removed on `keydown` in specific cases.
|
||||
* [#10285](http://dev.ckeditor.com/ticket/10285): Fixed: Styled text pasted from MS Word causes an infinite loop.
|
||||
* [#10131](http://dev.ckeditor.com/ticket/10131): Fixed: `undoManager#update` does not refresh the command state.
|
||||
* [#10337](http://dev.ckeditor.com/ticket/10337): Fixed: Unable to remove `<s>` using `removeformat`.
|
||||
|
||||
## CKEditor 4.1
|
||||
|
||||
* [#10192](http://dev.ckeditor.com/ticket/10192): Closing lists with Enter key does not work with Advanced Content Filter in several cases.
|
||||
* [#10191](http://dev.ckeditor.com/ticket/10191): Fixed allowed content rules unification, so the `filter.allowedContent` property always contains rules in the same format.
|
||||
* [#10224](http://dev.ckeditor.com/ticket/10224): Advanced Content Filter does not remove non-empty `<a>` elements anymore.
|
||||
* Minor issues in plugin integration with Advanced Content Filter:
|
||||
* [#10166](http://dev.ckeditor.com/ticket/10166): Added transformation from the `align` attribute to `float` style to preserve backward compatibility after the introduction of Advanced Content Filter.
|
||||
* [#10195](http://dev.ckeditor.com/ticket/10195): Image plugin no longer registers rules for links to Advanced Content Filter.
|
||||
* [#10213](http://dev.ckeditor.com/ticket/10213): Justify plugin is now correctly registering rules to Advanced Content Filter when `config.justifyClasses` is defined.
|
||||
|
||||
## CKEditor 4.1 RC
|
||||
|
||||
* [#9829](http://dev.ckeditor.com/ticket/9829): Data and features activation based on editor configuration.
|
||||
|
||||
Brand new data filtering system that works in 2 modes:
|
||||
|
||||
* based on loaded features (toolbar items, plugins) - the data will be filtered according to what the editor in its
|
||||
current configuration can handle,
|
||||
* based on `config.allowedContent` rules - the data will be filtered and the editor features (toolbar items, commands,
|
||||
keystrokes) will be enabled if they are allowed.
|
||||
|
||||
See the `datafiltering.html` sample, [guides](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) and [`CKEDITOR.filter` API documentation](http://docs.ckeditor.com/#!/api/CKEDITOR.filter).
|
||||
* [#9387](http://dev.ckeditor.com/ticket/9387): Reintroduced "Shared Spaces" - the ability to display toolbar and bottom editor space in selected locations and to share them by different editor instances.
|
||||
* [#9907](http://dev.ckeditor.com/ticket/9907): Added the `contentPreview` event for preview data manipulation.
|
||||
* [#9713](http://dev.ckeditor.com/ticket/9713): Introduced the `sourcedialog` plugin that brings raw HTML editing for inline editor instances.
|
||||
* Included in [#9829](http://dev.ckeditor.com/ticket/9829): Introduced new events, `toHtml` and `toDataFormat`, allowing for better integration with data processing. See API documentation: [`toHtml`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-toHtml), [`toDataFormat`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-toDataFormat).
|
||||
* [#9981](http://dev.ckeditor.com/ticket/9981): Added ability to filter `htmlParser.fragment`, `htmlParser.element` etc. by many `htmlParser.filter`s before writing structure to an HTML string.
|
||||
* Included in [#10103](http://dev.ckeditor.com/ticket/10103):
|
||||
* Introduced the `editor.status` property to make it easier to check the current status of the editor. See [API documentation](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-status).
|
||||
* Default `command` state is now `CKEDITOR.TRISTATE_DISABLE`. It will be activated on `editor.instanceReady` or immediately after being added if the editor is already initialized.
|
||||
* [#9796](http://dev.ckeditor.com/ticket/9796): Introduced `<s>` as a default tag for strikethrough, which replaces obsolete `<strike>` in HTML5.
|
||||
|
||||
## CKEditor 4.0.3
|
||||
|
||||
* [#10196](http://dev.ckeditor.com/ticket/10196): Fixed context menus not opening with keyboard shortcuts when Autogrow is enabled.
|
||||
* [#10212](http://dev.ckeditor.com/ticket/10212): [IE7-10] Undo command throws errors after multiple switches between Source and WYSIWYG view.
|
||||
* [#10219](http://dev.ckeditor.com/ticket/10219): [Inline editor] Error thrown after calling editor.destroy().
|
||||
|
||||
## CKEditor 4.0.2
|
||||
|
||||
* [#9779](http://dev.ckeditor.com/ticket/9779): Fixed overriding `CKEDITOR.getUrl` with `CKEDITOR_GETURL`.
|
||||
* [#9772](http://dev.ckeditor.com/ticket/9772): Custom buttons in dialog window footer have different look and size (Moono, Kama).
|
||||
* [#9029](http://dev.ckeditor.com/ticket/9029): Custom styles added with `styleSet.add()` are displayed in wrong order.
|
||||
* [#9887](http://dev.ckeditor.com/ticket/9887): Disable magicline when `editor.readOnly` is set.
|
||||
* [#9882](http://dev.ckeditor.com/ticket/9882): Fixed empty document title on `getData()` if set via the Document Properties dialog window.
|
||||
* [#9773](http://dev.ckeditor.com/ticket/9773): Fixed rendering problems with selection fields in the Kama skin.
|
||||
* [#9851](http://dev.ckeditor.com/ticket/9851): The `selectionChange` event is not fired when mouse selection ended outside editable.
|
||||
* [#9903](http://dev.ckeditor.com/ticket/9903): [Inline editor] Bad positioning of floating space with page horizontal scroll.
|
||||
* [#9872](http://dev.ckeditor.com/ticket/9872): `editor.checkDirty()` returns `true` when called onload. Removed the obsolete `editor.mayBeDirty` flag.
|
||||
* [#9893](http://dev.ckeditor.com/ticket/9893): Fixed broken toolbar when editing mixed direction content in Quirks mode.
|
||||
* [#9845](http://dev.ckeditor.com/ticket/9845): Fixed TAB navigation in the Link dialog window when the Anchor option is used and no anchors are available.
|
||||
* [#9883](http://dev.ckeditor.com/ticket/9883): Maximizing was making the entire page editable with divarea-based editors.
|
||||
* [#9940](http://dev.ckeditor.com/ticket/9940): [Firefox] Navigating back to a page with the editor was making the entire page editable.
|
||||
* [#9966](http://dev.ckeditor.com/ticket/9966): Fixed: Unable to type square brackets with French keyboard layout. Changed magicline keystrokes.
|
||||
* [#9507](http://dev.ckeditor.com/ticket/9507): [Firefox] Selection is moved before editable position when the editor is focused for the first time.
|
||||
* [#9947](http://dev.ckeditor.com/ticket/9947): [Webkit] Editor overflows parent container in some edge cases.
|
||||
* [#10105](http://dev.ckeditor.com/ticket/10105): Fixed: Broken sourcearea view when an RTL language is set.
|
||||
* [#10123](http://dev.ckeditor.com/ticket/10123): [Webkit] Fixed: Several dialog windows have broken layout since the latest Webkit release.
|
||||
* [#10152](http://dev.ckeditor.com/ticket/10152): Fixed: Invalid ARIA property used on menu items.
|
||||
|
||||
## CKEditor 4.0.1.1
|
||||
|
||||
* Security update: Added protection against XSS attack and possible path disclosure in PHP sample.
|
||||
|
||||
## CKEditor 4.0.1
|
||||
|
||||
Fixed issues:
|
||||
|
||||
* [#9655](http://dev.ckeditor.com/ticket/9655): Support for IE Quirks Mode in new Moono skin.
|
||||
* Accessibility issues (mainly on inline editor): [#9364](http://dev.ckeditor.com/ticket/9364), [#9368](http://dev.ckeditor.com/ticket/9368), [#9369](http://dev.ckeditor.com/ticket/9369), [#9370](http://dev.ckeditor.com/ticket/9370), [#9541](http://dev.ckeditor.com/ticket/9541), [#9543](http://dev.ckeditor.com/ticket/9543), [#9841](http://dev.ckeditor.com/ticket/9841), [#9844](http://dev.ckeditor.com/ticket/9844).
|
||||
* Magic-line:
|
||||
* [#9481](http://dev.ckeditor.com/ticket/9481): Added accessibility support for Magic-line.
|
||||
* [#9509](http://dev.ckeditor.com/ticket/9509): Added Magic-line support for forms.
|
||||
* [#9573](http://dev.ckeditor.com/ticket/9573): Magic-line doesn't disappear on `mouseout` in the specific case.
|
||||
* [#9754](http://dev.ckeditor.com/ticket/9754): [Webkit] Cut & paste simple unformatted text generates inline wrapper in Webkits.
|
||||
* [#9456](http://dev.ckeditor.com/ticket/9456): [Chrome] Properly paste bullet list style from MS-Word.
|
||||
* [#9699](http://dev.ckeditor.com/ticket/9699), [#9758](http://dev.ckeditor.com/ticket/9758): Improved selection locking when selecting by dragging.
|
||||
* Context menu:
|
||||
* [#9712](http://dev.ckeditor.com/ticket/9712): Context menu open destroys editor focus.
|
||||
* [#9366](http://dev.ckeditor.com/ticket/9366): Context menu should be displayed over floating toolbar.
|
||||
* [#9706](http://dev.ckeditor.com/ticket/9706): Context menu generates JS error in inline mode when editor attached to header element.
|
||||
* [#9800](http://dev.ckeditor.com/ticket/9800): Hide float panel when resizing window.
|
||||
* [#9721](http://dev.ckeditor.com/ticket/9721): Padding in content of div based editor puts editing area under bottom UI space.
|
||||
* [#9528](http://dev.ckeditor.com/ticket/9528): Host page's `box-sizing` style shouldn't influence editor UI elements.
|
||||
* [#9503](http://dev.ckeditor.com/ticket/9503): Forms plugin adds context menu listeners only on supported input types. Added support for `tel, email, search` and `url` input types.
|
||||
* [#9769](http://dev.ckeditor.com/ticket/9769): Improved floating toolbar positioning in narrow window.
|
||||
* [#9875](http://dev.ckeditor.com/ticket/9875): Table dialog doesn't populate width correctly.
|
||||
* [#8675](http://dev.ckeditor.com/ticket/8675): Deleting cells in nested table removes outer table cell.
|
||||
* [#9815](http://dev.ckeditor.com/ticket/9815): Can't edit dialog fields on editor initialized in jQuery UI modal dialog.
|
||||
* [#8888](http://dev.ckeditor.com/ticket/8888): CKEditor dialogs do not show completely in small window.
|
||||
* [#9360](http://dev.ckeditor.com/ticket/9360): [Inline editor] Blocks shown for a div stay permanently even after user exists editing the div.
|
||||
* [#9531](http://dev.ckeditor.com/ticket/9531): [Firefox & Inline editor] Toolbar is lost when closing format combo by clicking on its button.
|
||||
* [#9553](http://dev.ckeditor.com/ticket/9553): Table width incorrectly set when `border-width` style is specified.
|
||||
* [#9594](http://dev.ckeditor.com/ticket/9594): Cannot tab past CKEditor when it is in read only mode.
|
||||
* [#9658](http://dev.ckeditor.com/ticket/9658): [IE9] Justify not working on selected image.
|
||||
* [#9686](http://dev.ckeditor.com/ticket/9686): Added missing contents styles for `<pre>`.
|
||||
* [#9709](http://dev.ckeditor.com/ticket/9709): PasteFromWord should not depend on configuration from other styles.
|
||||
* [#9726](http://dev.ckeditor.com/ticket/9726): Removed color dialog dependency from table tools.
|
||||
* [#9765](http://dev.ckeditor.com/ticket/9765): Toolbar Collapse command documented incorrectly on Accessibility Instructions dialog.
|
||||
* [#9771](http://dev.ckeditor.com/ticket/9771): [Webkit & Opera] Fixed scrolling issues when pasting.
|
||||
* [#9787](http://dev.ckeditor.com/ticket/9787): [IE9] onChange isn't fired for checkboxes in dialogs.
|
||||
* [#9842](http://dev.ckeditor.com/ticket/9842): [Firefox 17] When we open toolbar menu for the first time & press down arrow key, focus goes to next toolbar button instead of menu options.
|
||||
* [#9847](http://dev.ckeditor.com/ticket/9847): Elements path shouldn't be initialized on inline editor.
|
||||
* [#9853](http://dev.ckeditor.com/ticket/9853): `Editor#addRemoveFormatFilter` is exposed before it really works.
|
||||
* [#8893](http://dev.ckeditor.com/ticket/8893): Value of `pasteFromWordCleanupFile` config is now taken from instance configuration.
|
||||
* [#9693](http://dev.ckeditor.com/ticket/9693): Removed "live preview" checkbox from UI color picker.
|
||||
|
||||
|
||||
## CKEditor 4.0
|
||||
|
||||
The first stable release of the new CKEditor 4 code line.
|
||||
|
||||
The CKEditor JavaScript API has been kept compatible with CKEditor 4, whenever
|
||||
possible. The list of relevant changes can be found in the [API Changes page of
|
||||
the CKEditor 4 documentation][1].
|
||||
|
||||
[1]: http://docs.ckeditor.com/#!/guide/dev_api_changes "API Changes"
|
||||
1274
lib/ckeditor4/LICENSE.md
Executable file
1274
lib/ckeditor4/LICENSE.md
Executable file
File diff suppressed because it is too large
Load Diff
39
lib/ckeditor4/README.md
Executable file
39
lib/ckeditor4/README.md
Executable file
@@ -0,0 +1,39 @@
|
||||
CKEditor 4
|
||||
==========
|
||||
|
||||
Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
http://ckeditor.com - See LICENSE.md for license information.
|
||||
|
||||
CKEditor is a text editor to be used inside web pages. It's not a replacement
|
||||
for desktop text editors like Word or OpenOffice, but a component to be used as
|
||||
part of web applications and websites.
|
||||
|
||||
## Documentation
|
||||
|
||||
The full editor documentation is available online at the following address:
|
||||
http://docs.ckeditor.com
|
||||
|
||||
## Installation
|
||||
|
||||
Installing CKEditor is an easy task. Just follow these simple steps:
|
||||
|
||||
1. **Download** the latest version from the CKEditor website:
|
||||
http://ckeditor.com. You should have already completed this step, but be
|
||||
sure you have the very latest version.
|
||||
2. **Extract** (decompress) the downloaded file into the root of your website.
|
||||
|
||||
**Note:** CKEditor is by default installed in the `ckeditor` folder. You can
|
||||
place the files in whichever you want though.
|
||||
|
||||
## Checking Your Installation
|
||||
|
||||
The editor comes with a few sample pages that can be used to verify that
|
||||
installation proceeded properly. Take a look at the `samples` directory.
|
||||
|
||||
To test your installation, just call the following page at your website:
|
||||
|
||||
http://<your site>/<CKEditor installation path>/samples/index.html
|
||||
|
||||
For example:
|
||||
|
||||
http://www.example.com/ckeditor/samples/index.html
|
||||
214
lib/ckeditor4/build-config.js
Executable file
214
lib/ckeditor4/build-config.js
Executable file
@@ -0,0 +1,214 @@
|
||||
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.html or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file was added automatically by CKEditor builder.
|
||||
* You may re-use it at any time at http://ckeditor.com/builder to build CKEditor again.
|
||||
*
|
||||
* NOTE:
|
||||
* This file is not used by CKEditor, you may remove it.
|
||||
* Changing this file will not change your CKEditor configuration.
|
||||
*/
|
||||
|
||||
var CKBUILDER_CONFIG = {
|
||||
skin: 'moono',
|
||||
preset: 'full',
|
||||
ignore: [
|
||||
'dev',
|
||||
'.gitignore',
|
||||
'.gitattributes',
|
||||
'README.md',
|
||||
'.mailmap'
|
||||
],
|
||||
plugins : {
|
||||
'about' : 1,
|
||||
'a11yhelp' : 1,
|
||||
'dialogadvtab' : 1,
|
||||
'basicstyles' : 1,
|
||||
'bidi' : 1,
|
||||
'blockquote' : 1,
|
||||
'clipboard' : 1,
|
||||
'colorbutton' : 1,
|
||||
'colordialog' : 1,
|
||||
'templates' : 1,
|
||||
'contextmenu' : 1,
|
||||
'div' : 1,
|
||||
'resize' : 1,
|
||||
'toolbar' : 1,
|
||||
'elementspath' : 1,
|
||||
'enterkey' : 1,
|
||||
'entities' : 1,
|
||||
'filebrowser' : 1,
|
||||
'find' : 1,
|
||||
'flash' : 1,
|
||||
'floatingspace' : 1,
|
||||
'font' : 1,
|
||||
'forms' : 1,
|
||||
'format' : 1,
|
||||
'horizontalrule' : 1,
|
||||
'htmlwriter' : 1,
|
||||
'iframe' : 1,
|
||||
'wysiwygarea' : 1,
|
||||
'image' : 1,
|
||||
'indentblock' : 1,
|
||||
'indentlist' : 1,
|
||||
'smiley' : 1,
|
||||
'justify' : 1,
|
||||
'link' : 1,
|
||||
'list' : 1,
|
||||
'liststyle' : 1,
|
||||
'magicline' : 1,
|
||||
'maximize' : 1,
|
||||
'newpage' : 1,
|
||||
'pagebreak' : 1,
|
||||
'pastetext' : 1,
|
||||
'pastefromword' : 1,
|
||||
'preview' : 1,
|
||||
'print' : 1,
|
||||
'removeformat' : 1,
|
||||
'save' : 1,
|
||||
'selectall' : 1,
|
||||
'showblocks' : 1,
|
||||
'showborders' : 1,
|
||||
'sourcearea' : 1,
|
||||
'specialchar' : 1,
|
||||
'scayt' : 1,
|
||||
'stylescombo' : 1,
|
||||
'tab' : 1,
|
||||
'table' : 1,
|
||||
'tabletools' : 1,
|
||||
'undo' : 1,
|
||||
'wsc' : 1,
|
||||
'dialog' : 1,
|
||||
'dialogui' : 1,
|
||||
'panelbutton' : 1,
|
||||
'button' : 1,
|
||||
'floatpanel' : 1,
|
||||
'panel' : 1,
|
||||
'menu' : 1,
|
||||
'popup' : 1,
|
||||
'fakeobjects' : 1,
|
||||
'richcombo' : 1,
|
||||
'listblock' : 1,
|
||||
'indent' : 1,
|
||||
'menubutton' : 1,
|
||||
'youtube' : 1,
|
||||
'uicolor' : 1,
|
||||
'tableresize' : 1,
|
||||
'syntaxhighlight' : 1,
|
||||
'stylesheetparser' : 1,
|
||||
'strinsert' : 1,
|
||||
'backup' : 1,
|
||||
'stat' : 1,
|
||||
'sourcedialog' : 1,
|
||||
'slideshow' : 1,
|
||||
'sharedspace' : 1,
|
||||
'resizewithwindow' : 1,
|
||||
'placeholder' : 1,
|
||||
'onchange' : 1,
|
||||
'mrmonkey' : 1,
|
||||
'mediaembed' : 1,
|
||||
'oembed' : 1,
|
||||
'maxheight' : 1,
|
||||
'mathedit' : 1,
|
||||
'lite' : 1,
|
||||
'symbol' : 1,
|
||||
'insertpre' : 1,
|
||||
'imagebrowser' : 1,
|
||||
'iframedialog' : 1,
|
||||
'htmlbuttons' : 1,
|
||||
'gg' : 1,
|
||||
'floating-tools' : 1,
|
||||
'fastimage' : 1,
|
||||
'eqneditor' : 1,
|
||||
'docprops' : 1,
|
||||
'divarea' : 1,
|
||||
'devtools' : 1,
|
||||
'confighelper' : 1,
|
||||
'codemirror' : 1,
|
||||
'pbckcode' : 1,
|
||||
'bbcode' : 1,
|
||||
'backgrounds' : 1,
|
||||
'autosave' : 1,
|
||||
'autogrow' : 1,
|
||||
'allmedias' : 1,
|
||||
'xml' : 1,
|
||||
'ajax' : 1,
|
||||
'texttransform' : 1,
|
||||
'toolbarswitch' : 1,
|
||||
'uploadcare' : 1,
|
||||
'wordcount' : 1,
|
||||
'xdsoft_translater' : 1,
|
||||
'zoom' : 1
|
||||
},
|
||||
languages : {
|
||||
'af' : 1,
|
||||
'sq' : 1,
|
||||
'ar' : 1,
|
||||
'eu' : 1,
|
||||
'bn' : 1,
|
||||
'bs' : 1,
|
||||
'bg' : 1,
|
||||
'ca' : 1,
|
||||
'zh-cn' : 1,
|
||||
'zh' : 1,
|
||||
'hr' : 1,
|
||||
'cs' : 1,
|
||||
'da' : 1,
|
||||
'nl' : 1,
|
||||
'en' : 1,
|
||||
'en-au' : 1,
|
||||
'en-ca' : 1,
|
||||
'en-gb' : 1,
|
||||
'eo' : 1,
|
||||
'et' : 1,
|
||||
'fo' : 1,
|
||||
'fi' : 1,
|
||||
'fr' : 1,
|
||||
'fr-ca' : 1,
|
||||
'gl' : 1,
|
||||
'ka' : 1,
|
||||
'de' : 1,
|
||||
'el' : 1,
|
||||
'gu' : 1,
|
||||
'he' : 1,
|
||||
'hi' : 1,
|
||||
'hu' : 1,
|
||||
'is' : 1,
|
||||
'id' : 1,
|
||||
'it' : 1,
|
||||
'ja' : 1,
|
||||
'km' : 1,
|
||||
'ko' : 1,
|
||||
'ku' : 1,
|
||||
'lv' : 1,
|
||||
'lt' : 1,
|
||||
'mk' : 1,
|
||||
'ms' : 1,
|
||||
'mn' : 1,
|
||||
'no' : 1,
|
||||
'nb' : 1,
|
||||
'fa' : 1,
|
||||
'pl' : 1,
|
||||
'pt-br' : 1,
|
||||
'pt' : 1,
|
||||
'ro' : 1,
|
||||
'ru' : 1,
|
||||
'sr' : 1,
|
||||
'sr-latn' : 1,
|
||||
'si' : 1,
|
||||
'sk' : 1,
|
||||
'sl' : 1,
|
||||
'es' : 1,
|
||||
'sv' : 1,
|
||||
'th' : 1,
|
||||
'tr' : 1,
|
||||
'ug' : 1,
|
||||
'uk' : 1,
|
||||
'vi' : 1,
|
||||
'cy' : 1,
|
||||
}
|
||||
};
|
||||
43
lib/ckeditor4/ckeditor.js
vendored
Executable file
43
lib/ckeditor4/ckeditor.js
vendored
Executable file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
// Compressed version of core/ckeditor_base.js. See original for instructions.
|
||||
/*jsl:ignore*/
|
||||
window.CKEDITOR_BASEPATH = '/lib/ckeditor4/'
|
||||
window.CKEDITOR||(window.CKEDITOR=function(){var l=Math.floor(900*Math.random())+100,b=window.CKEDITOR_BASEPATH||"";if(!b)for(var g=document.getElementsByTagName("script"),e=0;e<g.length;e++){var h=g[e].src.match(/(^|.*[\\\/])ckeditor(?:_basic)?(?:_source)?.js(?:\?.*)?$/i);if(h){b=h[1];break}}-1==b.indexOf(":/")&&(b=0===b.indexOf("/")?location.href.match(/^.*?:\/\/[^\/]*/)[0]+b:location.href.match(/^[^\?]*\/(?:)/)[0]+b);if(!b)throw'The CKEditor installation path could not be automatically detected. Please set the global variable "CKEDITOR_BASEPATH" before creating editor instances.';var c=function(){try{document.addEventListener?(document.removeEventListener("DOMContentLoaded",c,!1),j()):document.attachEvent&&"complete"===document.readyState&&(document.detachEvent("onreadystatechange",c),j())}catch(a){}},j=function(){for(var a;a=f.shift();)a()},f=[],d={timestamp:"",version:"%VERSION%",revision:"%REV%",rnd:l,_:{pending:[]},status:"unloaded",basePath:b,getUrl:function(a){-1==a.indexOf(":/")&&0!==a.indexOf("/")&&(a=this.basePath+a);this.timestamp&&("/"!=a.charAt(a.length-1)&&!/[&?]t=/.test(a))&&(a+=(0<=a.indexOf("?")?"&":"?")+"t="+this.timestamp);return a},domReady:function(a){f.push(a);"complete"===document.readyState&&setTimeout(c,1);if(1==f.length)if(document.addEventListener)document.addEventListener("DOMContentLoaded",c,!1),window.addEventListener("load",c,!1);else if(document.attachEvent){document.attachEvent("onreadystatechange",c);window.attachEvent("onload",c);a=!1;try{a=!window.frameElement}catch(b){}if(document.documentElement.doScroll&&a){var d=function(){try{document.documentElement.doScroll("left")}catch(a){setTimeout(d,1);return}c()};d()}}}},k=window.CKEDITOR_GETURL;if(k){var m=d.getUrl;d.getUrl=function(a){return k.call(d,a)||m.call(d,a)}}return d}());
|
||||
/*jsl:end*/
|
||||
|
||||
if ( CKEDITOR.loader )
|
||||
CKEDITOR.loader.load( 'ckeditor' );
|
||||
else {
|
||||
// Set the script name to be loaded by the loader.
|
||||
CKEDITOR._autoLoad = 'ckeditor';
|
||||
|
||||
// Include the loader script.
|
||||
if ( document.body && ( !document.readyState || document.readyState == 'complete' ) ) {
|
||||
var script = document.createElement( 'script' );
|
||||
script.type = 'text/javascript';
|
||||
script.src = CKEDITOR.getUrl( 'core/loader.js' ); //@added 'lib/ckeditor4'
|
||||
document.body.appendChild( script );
|
||||
} else {
|
||||
document.write( '<script type="text/javascript" src="' + CKEDITOR.getUrl( 'core/loader.js' ) + '"></script>' ); //@added 'lib/ckeditor4'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The skin to load for all created instances, it may be the name of the skin
|
||||
* folder inside the editor installation path, or the name and the path separated
|
||||
* by a comma.
|
||||
*
|
||||
* **Note:** This is a global configuration that applies to all instances.
|
||||
*
|
||||
* CKEDITOR.skinName = 'moono';
|
||||
*
|
||||
* CKEDITOR.skinName = 'myskin,/customstuff/myskin/';
|
||||
*
|
||||
* @cfg {String} [skinName='moono']
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.skinName = 'moono';
|
||||
19
lib/ckeditor4/config.js
Executable file
19
lib/ckeditor4/config.js
Executable file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.html or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
CKEDITOR.editorConfig = function( config ) {
|
||||
|
||||
// %REMOVE_START%
|
||||
// The configuration options below are needed when running CKEditor from source files.
|
||||
//config.plugins = 'dialogui,dialog,about,a11yhelp,dialogadvtab,basicstyles,bidi,blockquote,clipboard,button,panelbutton,panel,floatpanel,colorbutton,colordialog,templates,menu,contextmenu,div,resize,toolbar,elementspath,enterkey,entities,popup,filebrowser,find,fakeobjects,flash,floatingspace,listblock,richcombo,font,forms,format,horizontalrule,htmlwriter,iframe,wysiwygarea,image,indent,indentblock,indentlist,smiley,justify,link,list,liststyle,magicline,maximize,newpage,pagebreak,pastetext,pastefromword,preview,print,removeformat,save,selectall,showblocks,showborders,sourcearea,specialchar,menubutton,scayt,stylescombo,tab,table,tabletools,undo,wsc,youtube,uicolor,tableresize,syntaxhighlight,stylesheetparser,strinsert,backup,stat,sourcedialog,slideshow,sharedspace,resizewithwindow,placeholder,onchange,mrmonkey,mediaembed,oembed,maxheight,mathedit,lite,symbol,insertpre,imagebrowser,iframedialog,htmlbuttons,gg,floating-tools,fastimage,eqneditor,docprops,divarea,devtools,confighelper,codemirror,pbckcode,bbcode,backgrounds,autosave,autogrow,allmedias,xml,ajax,texttransform,toolbarswitch,uploadcare,wordcount,xdsoft_translater,zoom';
|
||||
config.plugins = 'dialogui,dialog,about,a11yhelp,dialogadvtab,basicstyles,bidi,blockquote,clipboard,button,panelbutton,panel,floatpanel,colorbutton,colordialog,templates,menu,contextmenu,div,resize,toolbar,elementspath,list,indent,enterkey,entities,popup,filebrowser,find,fakeobjects,flash,floatingspace,listblock,richcombo,font,forms,format,htmlwriter,horizontalrule,iframe,wysiwygarea,image,smiley,justify,link,liststyle,magicline,maximize,newpage,pagebreak,pastetext,pastefromword,preview,print,removeformat,save,selectall,showblocks,showborders,specialchar,menubutton,scayt,stylescombo,tab,table,tabletools,undo,wsc,autogrow,xml,ajax,youtube,stat,sourcedialog,sourcearea,codemirror';
|
||||
config.skin = 'moono';
|
||||
// %REMOVE_END%
|
||||
|
||||
// Define changes to default configuration here. For example:
|
||||
// config.language = 'fr';
|
||||
// config.uiColor = '#AADC6E';
|
||||
};
|
||||
|
||||
103
lib/ckeditor4/contents.css
Executable file
103
lib/ckeditor4/contents.css
Executable file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
body
|
||||
{
|
||||
/* Font */
|
||||
font-family: sans-serif, Arial, Verdana, "Trebuchet MS";
|
||||
font-size: 12px;
|
||||
|
||||
/* Text color */
|
||||
color: #333;
|
||||
|
||||
/* Remove the background color to make it transparent */
|
||||
background-color: #fff;
|
||||
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.cke_editable
|
||||
{
|
||||
font-size: 13px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
blockquote
|
||||
{
|
||||
font-style: italic;
|
||||
font-family: Georgia, Times, "Times New Roman", serif;
|
||||
padding: 2px 0;
|
||||
border-style: solid;
|
||||
border-color: #ccc;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.cke_contents_ltr blockquote
|
||||
{
|
||||
padding-left: 20px;
|
||||
padding-right: 8px;
|
||||
border-left-width: 5px;
|
||||
}
|
||||
|
||||
.cke_contents_rtl blockquote
|
||||
{
|
||||
padding-left: 8px;
|
||||
padding-right: 20px;
|
||||
border-right-width: 5px;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
color: #0782C1;
|
||||
}
|
||||
|
||||
ol,ul,dl
|
||||
{
|
||||
/* IE7: reset rtl list margin. (#7334) */
|
||||
*margin-right: 0px;
|
||||
/* preserved spaces for list items with text direction other than the list. (#6249,#8049)*/
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6
|
||||
{
|
||||
font-weight: normal;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
hr
|
||||
{
|
||||
border: 0px;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
img.right {
|
||||
border: 1px solid #ccc;
|
||||
float: right;
|
||||
margin-left: 15px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
img.left {
|
||||
border: 1px solid #ccc;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
img:hover {
|
||||
opacity: .9;
|
||||
filter: alpha(opacity = 90);
|
||||
}
|
||||
|
||||
pre
|
||||
{
|
||||
white-space: pre-wrap; /* CSS 2.1 */
|
||||
word-wrap: break-word; /* IE7 */
|
||||
}
|
||||
|
||||
.marker {
|
||||
background-color: Yellow;
|
||||
}
|
||||
69
lib/ckeditor4/core/_bootstrap.js
Executable file
69
lib/ckeditor4/core/_bootstrap.js
Executable file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview API initialization code.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
// Disable HC detection in WebKit. (#5429)
|
||||
if ( CKEDITOR.env.webkit )
|
||||
CKEDITOR.env.hc = false;
|
||||
else {
|
||||
// Check whether high contrast is active by creating a colored border.
|
||||
var hcDetect = CKEDITOR.dom.element.createFromHtml( '<div style="width:0px;height:0px;position:absolute;left:-10000px;' +
|
||||
'border: 1px solid;border-color: red blue;"></div>', 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;
|
||||
}
|
||||
|
||||
hcDetect.remove();
|
||||
}
|
||||
|
||||
if ( CKEDITOR.env.hc )
|
||||
CKEDITOR.env.cssClass += ' cke_hc';
|
||||
|
||||
// Initially hide UI spaces when relevant skins are loading, later restored by skin css.
|
||||
CKEDITOR.document.appendStyleText( '.cke{visibility:hidden;}' );
|
||||
|
||||
// Mark the editor as fully loaded.
|
||||
CKEDITOR.status = 'loaded';
|
||||
CKEDITOR.fireOnce( '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.editor.prototype.constructor.apply( pending[ i ][ 0 ], pending[ i ][ 1 ] );
|
||||
CKEDITOR.add( pending[ i ][ 0 ] );
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running on a High Contrast environment.
|
||||
*
|
||||
* if ( CKEDITOR.env.hc )
|
||||
* alert( 'You\'re running on High Contrast mode. The editor interface will get adapted to provide you a better experience.' );
|
||||
*
|
||||
* @property {Boolean} hc
|
||||
* @member CKEDITOR.env
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when a CKEDITOR core object is fully loaded and ready for interaction.
|
||||
*
|
||||
* @event loaded
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
204
lib/ckeditor4/core/ckeditor.js
vendored
Executable file
204
lib/ckeditor4/core/ckeditor.js
vendored
Executable file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Contains the third and last part of the {@link CKEDITOR} object
|
||||
* definition.
|
||||
*/
|
||||
|
||||
/** @class CKEDITOR */
|
||||
|
||||
// Remove the CKEDITOR.loadFullCore reference defined on ckeditor_basic.
|
||||
delete CKEDITOR.loadFullCore;
|
||||
|
||||
/**
|
||||
* Stores references to all editor instances created. The name of the properties
|
||||
* in this object correspond to instance names, and their values contain the
|
||||
* {@link CKEDITOR.editor} object representing them.
|
||||
*
|
||||
* alert( CKEDITOR.instances.editor1.name ); // 'editor1'
|
||||
*
|
||||
* @property {Object}
|
||||
*/
|
||||
CKEDITOR.instances = {};
|
||||
|
||||
/**
|
||||
* The document of the window storing the CKEDITOR object.
|
||||
*
|
||||
* alert( CKEDITOR.document.getBody().getName() ); // 'body'
|
||||
*
|
||||
* @property {CKEDITOR.dom.document}
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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' );
|
||||
}
|
||||
});
|
||||
|
||||
CKEDITOR.fire( 'instance', null, editor );
|
||||
};
|
||||
|
||||
/**
|
||||
* 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#method-destroy}.
|
||||
*
|
||||
* @private
|
||||
* @param {CKEDITOR.editor} editor The editor instance to be removed.
|
||||
*/
|
||||
CKEDITOR.remove = function( editor ) {
|
||||
delete CKEDITOR.instances[ editor.name ];
|
||||
};
|
||||
|
||||
(function() {
|
||||
var tpls = {};
|
||||
|
||||
/**
|
||||
* Adds a named {@link CKEDITOR.template} instance to be reused among all editors.
|
||||
* This will return the existing one if a template with same name is already
|
||||
* defined. Additionally, it fires the "template" event to allow template source customization.
|
||||
*
|
||||
* @param {String} name The name which identifies a UI template.
|
||||
* @param {String} source The source string for constructing this template.
|
||||
* @returns {CKEDITOR.template} The created template instance.
|
||||
*/
|
||||
CKEDITOR.addTemplate = function( name, source ) {
|
||||
var tpl = tpls[ name ];
|
||||
if ( tpl )
|
||||
return tpl;
|
||||
|
||||
// Make it possible to customize the template through event.
|
||||
var params = { name: name, source: source };
|
||||
CKEDITOR.fire( 'template', params );
|
||||
|
||||
return ( tpls[ name ] = new CKEDITOR.template( params.source ) );
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a defined template created with {@link CKEDITOR#addTemplate}.
|
||||
*
|
||||
* @param {String} name The template name.
|
||||
*/
|
||||
CKEDITOR.getTemplate = function( name ) {
|
||||
return tpls[ name ];
|
||||
};
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var styles = [];
|
||||
|
||||
/**
|
||||
* Adds CSS rules to be appended to the editor document.
|
||||
* This method is mostly used by plugins to add custom styles to the editor
|
||||
* document. For basic content styling the `contents.css` file should be
|
||||
* used instead.
|
||||
*
|
||||
* **Note:** This function should be called before the creation of editor instances.
|
||||
*
|
||||
* // Add styles for all headings inside editable contents.
|
||||
* CKEDITOR.addCss( '.cke_editable h1,.cke_editable h2,.cke_editable h3 { border-bottom: 1px dotted red }' );
|
||||
*
|
||||
* @param {String} css The style rules to be appended.
|
||||
* @see CKEDITOR.config#contentsCss
|
||||
*/
|
||||
CKEDITOR.addCss = function( css ) {
|
||||
styles.push( css );
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string will all CSS rules passed to the {@link CKEDITOR#addCss} method.
|
||||
*
|
||||
* @returns {String} A string containing CSS rules.
|
||||
*/
|
||||
CKEDITOR.getCss = function() {
|
||||
return styles.join( '\n' );
|
||||
};
|
||||
})();
|
||||
|
||||
// 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( '_bootstrap' ); // %REMOVE_LINE%
|
||||
|
||||
// Tri-state constants.
|
||||
/**
|
||||
* Used to indicate the ON or ACTIVE state.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=1]
|
||||
*/
|
||||
CKEDITOR.TRISTATE_ON = 1;
|
||||
|
||||
/**
|
||||
* Used to indicate the OFF or INACTIVE state.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=2]
|
||||
*/
|
||||
CKEDITOR.TRISTATE_OFF = 2;
|
||||
|
||||
/**
|
||||
* Used to indicate the DISABLED state.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=0]
|
||||
*/
|
||||
CKEDITOR.TRISTATE_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* The editor which is currently active (has user focus).
|
||||
*
|
||||
* function showCurrentEditorName() {
|
||||
* if ( CKEDITOR.currentInstance )
|
||||
* alert( CKEDITOR.currentInstance.name );
|
||||
* else
|
||||
* alert( 'Please focus an editor first.' );
|
||||
* }
|
||||
*
|
||||
* @property {CKEDITOR.editor} currentInstance
|
||||
* @see CKEDITOR#event-currentInstance
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the CKEDITOR.currentInstance object reference changes. This may
|
||||
* happen when setting the focus on different editor instances in the page.
|
||||
*
|
||||
* var editor; // A variable to store a reference to the current editor.
|
||||
* CKEDITOR.on( 'currentInstance', function() {
|
||||
* editor = CKEDITOR.currentInstance;
|
||||
* } );
|
||||
*
|
||||
* @event currentInstance
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the last instance has been destroyed. This event is used to perform
|
||||
* global memory cleanup.
|
||||
*
|
||||
* @event reset
|
||||
*/
|
||||
317
lib/ckeditor4/core/ckeditor_base.js
Executable file
317
lib/ckeditor4/core/ckeditor_base.js
Executable file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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.
|
||||
|
||||
// window.CKEDITOR||(window.CKEDITOR=function(){var l=Math.floor(900*Math.random())+100,b=window.CKEDITOR_BASEPATH||"";if(!b)for(var g=document.getElementsByTagName("script"),e=0;e<g.length;e++){var h=g[e].src.match(/(^|.*[\\\/])ckeditor(?:_basic)?(?:_source)?.js(?:\?.*)?$/i);if(h){b=h[1];break}}-1==b.indexOf(":/")&&(b=0===b.indexOf("/")?location.href.match(/^.*?:\/\/[^\/]*/)[0]+b:location.href.match(/^[^\?]*\/(?:)/)[0]+b);if(!b)throw'The CKEditor installation path could not be automatically detected. Please set the global variable "CKEDITOR_BASEPATH" before creating editor instances.';var c=function(){try{document.addEventListener?(document.removeEventListener("DOMContentLoaded",c,!1),j()):document.attachEvent&&"complete"===document.readyState&&(document.detachEvent("onreadystatechange",c),j())}catch(a){}},j=function(){for(var a;a=f.shift();)a()},f=[],d={timestamp:"",version:"%VERSION%",revision:"%REV%",rnd:l,_:{pending:[]},status:"unloaded",basePath:b,getUrl:function(a){-1==a.indexOf(":/")&&0!==a.indexOf("/")&&(a=this.basePath+a);this.timestamp&&("/"!=a.charAt(a.length-1)&&!/[&?]t=/.test(a))&&(a+=(0<=a.indexOf("?")?"&":"?")+"t="+this.timestamp);return a},domReady:function(a){f.push(a);"complete"===document.readyState&&setTimeout(c,1);if(1==f.length)if(document.addEventListener)document.addEventListener("DOMContentLoaded",c,!1),window.addEventListener("load",c,!1);else if(document.attachEvent){document.attachEvent("onreadystatechange",c);window.attachEvent("onload",c);a=!1;try{a=!window.frameElement}catch(b){}if(document.documentElement.doScroll&&a){var d=function(){try{document.documentElement.doScroll("left")}catch(a){setTimeout(d,1);return}c()};d()}}}},k=window.CKEDITOR_GETURL;if(k){var m=d.getUrl;d.getUrl=function(a){return k.call(d,a)||m.call(d,a)}}return d}());
|
||||
|
||||
// The Closure Compiler online service should be used when updating this manually:
|
||||
// http://closure-compiler.appspot.com/
|
||||
|
||||
// #### Raw code
|
||||
// ATTENTION: read the above "Compressed Code" notes when changing this code.
|
||||
|
||||
if ( !window.CKEDITOR ) {
|
||||
/**
|
||||
* This is the API entry point. The entire CKEditor code runs under this object.
|
||||
* @class CKEDITOR
|
||||
* @singleton
|
||||
*/
|
||||
window.CKEDITOR = (function() {
|
||||
var 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.
|
||||
*
|
||||
* alert( CKEDITOR.timestamp ); // e.g. '87dm'
|
||||
*/
|
||||
timestamp: '', // %REMOVE_LINE%
|
||||
/* // %REMOVE_LINE%
|
||||
// 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: '%TIMESTAMP%',
|
||||
*/ // %REMOVE_LINE%
|
||||
|
||||
/**
|
||||
* Contains the CKEditor version number.
|
||||
*
|
||||
* alert( CKEDITOR.version ); // e.g. 'CKEditor 3.4.1'
|
||||
*/
|
||||
version: '%VERSION%',
|
||||
|
||||
/**
|
||||
* Contains the CKEditor revision number.
|
||||
* The revision number is incremented automatically, following each
|
||||
* modification to the CKEditor source code.
|
||||
*
|
||||
* alert( CKEDITOR.revision ); // e.g. '3975'
|
||||
*/
|
||||
revision: '%REV%',
|
||||
|
||||
/**
|
||||
* A 3-digit random integer, valid for the entire life of the CKEDITOR object.
|
||||
*
|
||||
* alert( CKEDITOR.rnd ); // e.g. 319
|
||||
*
|
||||
* @property {Number}
|
||||
*/
|
||||
rnd: Math.floor( Math.random() * ( 999 /*Max*/ - 100 /*Min*/ + 1 ) ) + 100 /*Min*/,
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
_: {
|
||||
pending: []
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* * **loaded**: the API can be fully used.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* if ( CKEDITOR.status == 'loaded' ) {
|
||||
* // The API can now be fully used.
|
||||
* doSomething();
|
||||
* } else {
|
||||
* // Wait for the full core to be loaded and fire its loading.
|
||||
* CKEDITOR.on( 'load', doSomething );
|
||||
* CKEDITOR.loadFullCore && CKEDITOR.loadFullCore();
|
||||
* }
|
||||
*/
|
||||
status: 'unloaded',
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* alert( CKEDITOR.basePath ); // e.g. 'http://www.example.com/ckeditor/'
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
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 <script> tag.
|
||||
var path = window.CKEDITOR_BASEPATH || '';
|
||||
|
||||
if ( !path ) {
|
||||
var scripts = document.getElementsByTagName( 'script' );
|
||||
|
||||
for ( var i = 0; i < scripts.length; i++ ) {
|
||||
var match = scripts[ i ].src.match( /(^|.*[\\\/])ckeditor(?:_basic)?(?:_source)?.js(?:\?.*)?$/i );
|
||||
|
||||
if ( match ) {
|
||||
path = match[ 1 ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In IE (only) the script.src string is the raw value entered in the
|
||||
// HTML source. Other browsers return the full resolved URL instead.
|
||||
if ( path.indexOf( ':/' ) == -1 ) {
|
||||
// Absolute path.
|
||||
if ( path.indexOf( '/' ) === 0 )
|
||||
path = location.href.match( /^.*?:\/\/[^\/]*/ )[ 0 ] + path;
|
||||
// Relative path.
|
||||
else
|
||||
path = location.href.match( /^[^\?]*\/(?:)/ )[ 0 ] + path;
|
||||
}
|
||||
|
||||
if ( !path )
|
||||
throw 'The CKEditor installation path could not be automatically detected. Please set the global variable "CKEDITOR_BASEPATH" before creating editor instances.';
|
||||
|
||||
return path;
|
||||
})(),
|
||||
|
||||
/**
|
||||
* Gets the full URL for CKEditor resources. By default, URLs
|
||||
* returned by this function contain a querystring parameter ("t")
|
||||
* set to the {@link CKEDITOR#timestamp} value.
|
||||
*
|
||||
* It is possible to provide a custom implementation of this
|
||||
* function by setting a global variable named `CKEDITOR_GETURL`.
|
||||
* This global variable must be set **before** the editor script
|
||||
* loading. If the custom implementation returns nothing (`==null`), the
|
||||
* default implementation is used.
|
||||
*
|
||||
* // e.g. 'http://www.example.com/ckeditor/skins/default/editor.css?t=87dm'
|
||||
* alert( CKEDITOR.getUrl( 'skins/default/editor.css' ) );
|
||||
*
|
||||
* // e.g. 'http://www.example.com/skins/default/editor.css?t=87dm'
|
||||
* alert( CKEDITOR.getUrl( '/skins/default/editor.css' ) );
|
||||
*
|
||||
* // e.g. 'http://www.somesite.com/skins/default/editor.css?t=87dm'
|
||||
* alert( CKEDITOR.getUrl( 'http://www.somesite.com/skins/default/editor.css' ) );
|
||||
*
|
||||
* @param {String} resource The resource whose full URL we want to get.
|
||||
* It may be a full, absolute, or relative URL.
|
||||
* @returns {String} The full URL.
|
||||
*/
|
||||
getUrl: function( resource ) {
|
||||
// If this is not a full or absolute path.
|
||||
if ( resource.indexOf( ':/' ) == -1 && resource.indexOf( '/' ) !== 0 )
|
||||
resource = this.basePath + resource;
|
||||
|
||||
// Add the timestamp, except for directories.
|
||||
if ( this.timestamp && resource.charAt( resource.length - 1 ) != '/' && !( /[&?]t=/ ).test( resource ) )
|
||||
resource += ( resource.indexOf( '?' ) >= 0 ? '&' : '?' ) + 't=' + this.timestamp;
|
||||
|
||||
return resource;
|
||||
},
|
||||
|
||||
/**
|
||||
* Specify a function to execute when the DOM is fully loaded.
|
||||
*
|
||||
* If called after the DOM has been initialized, the function passed in will
|
||||
* be executed immediately.
|
||||
*
|
||||
* @method
|
||||
* @todo
|
||||
*/
|
||||
domReady: (function() {
|
||||
// Based on the original jQuery code.
|
||||
|
||||
var callbacks = [];
|
||||
|
||||
function onReady() {
|
||||
try {
|
||||
// Cleanup functions for the document ready method
|
||||
if ( document.addEventListener ) {
|
||||
document.removeEventListener( 'DOMContentLoaded', onReady, false );
|
||||
executeCallbacks();
|
||||
}
|
||||
// Make sure body exists, at least, in case IE gets a little overzealous.
|
||||
else if ( document.attachEvent && document.readyState === 'complete' ) {
|
||||
document.detachEvent( 'onreadystatechange', onReady );
|
||||
executeCallbacks();
|
||||
}
|
||||
} catch ( er ) {}
|
||||
}
|
||||
|
||||
function executeCallbacks() {
|
||||
var i;
|
||||
while ( ( i = callbacks.shift() ) )
|
||||
i();
|
||||
}
|
||||
|
||||
return function( fn ) {
|
||||
callbacks.push( fn );
|
||||
|
||||
// Catch cases where this is called after the
|
||||
// browser event has already occurred.
|
||||
if ( document.readyState === 'complete' )
|
||||
// Handle it asynchronously to allow scripts the opportunity to delay ready
|
||||
setTimeout( onReady, 1 );
|
||||
|
||||
// Run below once on demand only.
|
||||
if ( callbacks.length != 1 )
|
||||
return;
|
||||
|
||||
// For IE>8, Firefox, Opera and Webkit.
|
||||
if ( document.addEventListener ) {
|
||||
// Use the handy event callback
|
||||
document.addEventListener( 'DOMContentLoaded', onReady, false );
|
||||
|
||||
// A fallback to window.onload, that will always work
|
||||
window.addEventListener( 'load', onReady, false );
|
||||
|
||||
}
|
||||
// If old IE event model is used
|
||||
else if ( document.attachEvent ) {
|
||||
// ensure firing before onload,
|
||||
// maybe late but safe also for iframes
|
||||
document.attachEvent( 'onreadystatechange', onReady );
|
||||
|
||||
// A fallback to window.onload, that will always work
|
||||
window.attachEvent( 'onload', onReady );
|
||||
|
||||
// If IE and not a frame
|
||||
// continually check to see if the document is ready
|
||||
// use the trick by Diego Perini
|
||||
// http://javascript.nwbox.com/IEContentLoaded/
|
||||
var toplevel = false;
|
||||
|
||||
try {
|
||||
toplevel = !window.frameElement;
|
||||
} catch ( e ) {}
|
||||
|
||||
if ( document.documentElement.doScroll && toplevel ) {
|
||||
function scrollCheck() {
|
||||
try {
|
||||
document.documentElement.doScroll( 'left' );
|
||||
} catch ( e ) {
|
||||
setTimeout( scrollCheck, 1 );
|
||||
return;
|
||||
}
|
||||
onReady();
|
||||
}
|
||||
scrollCheck();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})()
|
||||
};
|
||||
|
||||
// Make it possible to override the "url" function with a custom
|
||||
// implementation pointing to a global named CKEDITOR_GETURL.
|
||||
var newGetUrl = window.CKEDITOR_GETURL;
|
||||
if ( newGetUrl ) {
|
||||
var originalGetUrl = CKEDITOR.getUrl;
|
||||
CKEDITOR.getUrl = function( resource ) {
|
||||
return newGetUrl.call( CKEDITOR, resource ) || originalGetUrl.call( CKEDITOR, resource );
|
||||
};
|
||||
}
|
||||
|
||||
return CKEDITOR;
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called upon loading a custom configuration file that can
|
||||
* modify the editor instance configuration ({@link CKEDITOR.editor#config}).
|
||||
* It is usually defined inside the custom configuration files that can
|
||||
* include developer defined settings.
|
||||
*
|
||||
* // This is supposed to be placed in the config.js file.
|
||||
* CKEDITOR.editorConfig = function( config ) {
|
||||
* // Define changes to default configuration here. For example:
|
||||
* config.language = 'fr';
|
||||
* config.uiColor = '#AADC6E';
|
||||
* };
|
||||
*
|
||||
* @method editorConfig
|
||||
* @param {CKEDITOR.config} config A configuration object containing the
|
||||
* settings defined for a {@link CKEDITOR.editor} instance up to this
|
||||
* function call. Note that not all settings may still be available. See
|
||||
* [Configuration Loading Order](http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Setting_Configurations#Configuration_Loading_Order)
|
||||
* for details.
|
||||
*/
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR )
|
||||
94
lib/ckeditor4/core/ckeditor_basic.js
Executable file
94
lib/ckeditor4/core/ckeditor_basic.js
Executable file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Contains the second part of the {@link CKEDITOR} object
|
||||
* definition, which defines the basic editor features to be available in
|
||||
* the root ckeditor_basic.js file.
|
||||
*/
|
||||
|
||||
if ( CKEDITOR.status == 'unloaded' ) {
|
||||
(function() {
|
||||
CKEDITOR.event.implementOn( CKEDITOR );
|
||||
|
||||
/**
|
||||
* Forces the full CKEditor core code, in the case only the basic code has been
|
||||
* loaded (`ckeditor_basic.js`). This method self-destroys (becomes undefined) in
|
||||
* the first call or as soon as the full code is available.
|
||||
*
|
||||
* // Check if the full core code has been loaded and load it.
|
||||
* if ( CKEDITOR.loadFullCore )
|
||||
* CKEDITOR.loadFullCore();
|
||||
*
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.loadFullCore = function() {
|
||||
// If the basic code is not ready, just mark it to be loaded.
|
||||
if ( CKEDITOR.status != 'basic_ready' ) {
|
||||
CKEDITOR.loadFullCore._load = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy this function.
|
||||
delete CKEDITOR.loadFullCore;
|
||||
|
||||
// Append the script to the head.
|
||||
var script = document.createElement( 'script' );
|
||||
script.type = 'text/javascript';
|
||||
script.src = CKEDITOR.basePath + 'ckeditor.js';
|
||||
script.src = CKEDITOR.basePath + 'ckeditor_source.js'; // %REMOVE_LINE%
|
||||
|
||||
document.getElementsByTagName( 'head' )[ 0 ].appendChild( script );
|
||||
};
|
||||
|
||||
/**
|
||||
* The time to wait (in seconds) to load the full editor code after the
|
||||
* page load, if the "ckeditor_basic" file is used. If set to zero, the
|
||||
* editor is loaded on demand, as soon as an instance is created.
|
||||
*
|
||||
* This value must be set on the page before the page load completion.
|
||||
*
|
||||
* // Loads the full source after five seconds.
|
||||
* CKEDITOR.loadFullCoreTimeout = 5;
|
||||
*
|
||||
* @property
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.loadFullCoreTimeout = 0;
|
||||
|
||||
// Documented at ckeditor.js.
|
||||
CKEDITOR.add = function( editor ) {
|
||||
// For now, just put the editor in the pending list. It will be
|
||||
// processed as soon as the full code gets loaded.
|
||||
var pending = this._.pending || ( this._.pending = [] );
|
||||
pending.push( editor );
|
||||
};
|
||||
|
||||
(function() {
|
||||
var onload = function() {
|
||||
var loadFullCore = CKEDITOR.loadFullCore,
|
||||
loadFullCoreTimeout = CKEDITOR.loadFullCoreTimeout;
|
||||
|
||||
if ( !loadFullCore )
|
||||
return;
|
||||
|
||||
CKEDITOR.status = 'basic_ready';
|
||||
|
||||
if ( loadFullCore && loadFullCore._load )
|
||||
loadFullCore();
|
||||
else if ( loadFullCoreTimeout ) {
|
||||
setTimeout( function() {
|
||||
if ( CKEDITOR.loadFullCore )
|
||||
CKEDITOR.loadFullCore();
|
||||
}, loadFullCoreTimeout * 1000 );
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.domReady( onload );
|
||||
})();
|
||||
|
||||
CKEDITOR.status = 'basic_loaded';
|
||||
})();
|
||||
}
|
||||
255
lib/ckeditor4/core/command.js
Executable file
255
lib/ckeditor4/core/command.js
Executable file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a command that can be executed on an editor instance.
|
||||
*
|
||||
* var command = new CKEDITOR.command( editor, {
|
||||
* exec: function( editor ) {
|
||||
* alert( editor.document.getBody().getHtml() );
|
||||
* }
|
||||
* } );
|
||||
*
|
||||
* @class
|
||||
* @mixins CKEDITOR.event
|
||||
* @constructor Creates a command class instance.
|
||||
* @param {CKEDITOR.editor} editor The editor instance this command will be
|
||||
* related to.
|
||||
* @param {CKEDITOR.commandDefinition} commandDefinition The command
|
||||
* definition.
|
||||
*/
|
||||
CKEDITOR.command = function( editor, commandDefinition ) {
|
||||
/**
|
||||
* Lists UI items that are associated to this command. This list can be
|
||||
* used to interact with the UI on command execution (by the execution code
|
||||
* itself, for example).
|
||||
*
|
||||
* alert( 'Number of UI items associated to this command: ' + command.uiItems.length );
|
||||
*/
|
||||
this.uiItems = [];
|
||||
|
||||
/**
|
||||
* Executes the command.
|
||||
*
|
||||
* command.exec(); // The command gets executed.
|
||||
*
|
||||
* @param {Object} [data] Any data to pass to the command. Depends on the
|
||||
* command implementation and requirements.
|
||||
* @returns {Boolean} A boolean indicating that the command has been successfully executed.
|
||||
*/
|
||||
this.exec = function( data ) {
|
||||
if ( this.state == CKEDITOR.TRISTATE_DISABLED || !this.checkAllowed() )
|
||||
return false;
|
||||
|
||||
if ( this.editorFocus ) // Give editor focus if necessary (#4355).
|
||||
editor.focus();
|
||||
|
||||
if ( this.fire( 'exec' ) === false )
|
||||
return true;
|
||||
|
||||
return ( commandDefinition.exec.call( this, editor, data ) !== false );
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicitly update the status of the command, by firing the {@link CKEDITOR.command#event-refresh} event,
|
||||
* as well as invoke the {@link CKEDITOR.command#method-refresh} method if defined, this method
|
||||
* is to allow different parts of the editor code to contribute in command status resolution.
|
||||
*
|
||||
* @todo
|
||||
*/
|
||||
this.refresh = function( editor, path ) {
|
||||
// Do nothing is we're on read-only and this command doesn't support it.
|
||||
// We don't need to disabled the command explicitely here, because this
|
||||
// is already done by the "readOnly" event listener.
|
||||
if ( !this.readOnly && editor.readOnly )
|
||||
return true;
|
||||
|
||||
// Disable commands that are not allowed in the current selection path context.
|
||||
if ( this.context && !path.isContextFor( this.context ) ) {
|
||||
this.disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make the "enabled" state as basis.
|
||||
this.enable();
|
||||
|
||||
if ( this.fire( 'refresh', { editor: editor, path: path } ) === false )
|
||||
return true;
|
||||
|
||||
return ( commandDefinition.refresh && commandDefinition.refresh.apply( this, arguments ) !== false );
|
||||
};
|
||||
|
||||
var allowed;
|
||||
|
||||
/**
|
||||
* Checks whether this command is allowed by the allowed
|
||||
* content filter ({@link CKEDITOR.filter}). This means
|
||||
* that if command implements {@link CKEDITOR.feature} interface it will be tested
|
||||
* by {@link CKEDITOR.filter#checkFeature}.
|
||||
*
|
||||
* @since 4.1
|
||||
* @returns {Boolean} Whether this command is allowed.
|
||||
*/
|
||||
this.checkAllowed = function() {
|
||||
if ( typeof allowed == 'boolean' )
|
||||
return allowed;
|
||||
|
||||
return allowed = editor.filter.checkFeature( this );
|
||||
};
|
||||
|
||||
CKEDITOR.tools.extend( this, commandDefinition, {
|
||||
/**
|
||||
* The editor modes within which the command can be executed. The
|
||||
* execution will have no action if the current mode is not listed
|
||||
* in this property.
|
||||
*
|
||||
* // Enable the command in both WYSIWYG and Source modes.
|
||||
* command.modes = { wysiwyg:1,source:1 };
|
||||
*
|
||||
* // Enable the command in Source mode only.
|
||||
* command.modes = { source:1 };
|
||||
*
|
||||
* @see CKEDITOR.editor#mode
|
||||
*/
|
||||
modes: { wysiwyg:1 },
|
||||
|
||||
/**
|
||||
* Indicates that the editor will get the focus before executing
|
||||
* the command.
|
||||
*
|
||||
* // Do not force the editor to have focus when executing the command.
|
||||
* command.editorFocus = false;
|
||||
*
|
||||
* @property {Boolean} [=true]
|
||||
*/
|
||||
editorFocus: 1,
|
||||
|
||||
/**
|
||||
* Indicates that this command is sensible to the selection context.
|
||||
* If `true`, the {@link CKEDITOR.command#method-refresh} method will be
|
||||
* called for this command on the {@link CKEDITOR.editor#event-selectionChange} event.
|
||||
*
|
||||
* @property {Boolean} [=false]
|
||||
*/
|
||||
contextSensitive: !!commandDefinition.context,
|
||||
|
||||
/**
|
||||
* Indicates the editor state. Possible values are:
|
||||
*
|
||||
* * {@link CKEDITOR#TRISTATE_DISABLED}: the command is
|
||||
* disabled. It's execution will have no effect. Same as {@link #disable}.
|
||||
* * {@link CKEDITOR#TRISTATE_ON}: the command is enabled
|
||||
* and currently active in the editor (for context sensitive commands, for example).
|
||||
* * {@link CKEDITOR#TRISTATE_OFF}: the command is enabled
|
||||
* and currently inactive in the editor (for context sensitive commands, for example).
|
||||
*
|
||||
* Do not set this property directly, using the {@link #setState} method instead.
|
||||
*
|
||||
* if ( command.state == CKEDITOR.TRISTATE_DISABLED )
|
||||
* alert( 'This command is disabled' );
|
||||
*
|
||||
* @property {Number} [=CKEDITOR.TRISTATE_DISABLED]
|
||||
*/
|
||||
state: CKEDITOR.TRISTATE_DISABLED
|
||||
});
|
||||
|
||||
// Call the CKEDITOR.event constructor to initialize this instance.
|
||||
CKEDITOR.event.call( this );
|
||||
};
|
||||
|
||||
CKEDITOR.command.prototype = {
|
||||
/**
|
||||
* Enables the command for execution. The command state (see
|
||||
* {@link CKEDITOR.command#property-state}) available before disabling it is restored.
|
||||
*
|
||||
* command.enable();
|
||||
* command.exec(); // Execute the command.
|
||||
*/
|
||||
enable: function() {
|
||||
if ( this.state == CKEDITOR.TRISTATE_DISABLED && this.checkAllowed() )
|
||||
this.setState( ( !this.preserveState || ( typeof this.previousState == 'undefined' ) ) ? CKEDITOR.TRISTATE_OFF : this.previousState );
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables the command for execution. The command state (see
|
||||
* {@link CKEDITOR.command#property-state}) will be set to {@link CKEDITOR#TRISTATE_DISABLED}.
|
||||
*
|
||||
* command.disable();
|
||||
* command.exec(); // "false" - Nothing happens.
|
||||
*/
|
||||
disable: function() {
|
||||
this.setState( CKEDITOR.TRISTATE_DISABLED );
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the command state.
|
||||
*
|
||||
* command.setState( CKEDITOR.TRISTATE_ON );
|
||||
* command.exec(); // Execute the command.
|
||||
* command.setState( CKEDITOR.TRISTATE_DISABLED );
|
||||
* command.exec(); // 'false' - Nothing happens.
|
||||
* command.setState( CKEDITOR.TRISTATE_OFF );
|
||||
* command.exec(); // Execute the command.
|
||||
*
|
||||
* @param {Number} newState The new state. See {@link #property-state}.
|
||||
* @returns {Boolean} Returns `true` if the command state changed.
|
||||
*/
|
||||
setState: function( newState ) {
|
||||
// Do nothing if there is no state change.
|
||||
if ( this.state == newState || !this.checkAllowed() )
|
||||
return false;
|
||||
|
||||
this.previousState = this.state;
|
||||
|
||||
// Set the new state.
|
||||
this.state = newState;
|
||||
|
||||
// Fire the "state" event, so other parts of the code can react to the
|
||||
// change.
|
||||
this.fire( 'state' );
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the on/off (active/inactive) state of the command. This is
|
||||
* mainly used internally by context sensitive commands.
|
||||
*
|
||||
* command.toggleState();
|
||||
*/
|
||||
toggleState: function() {
|
||||
if ( this.state == CKEDITOR.TRISTATE_OFF )
|
||||
this.setState( CKEDITOR.TRISTATE_ON );
|
||||
else if ( this.state == CKEDITOR.TRISTATE_ON )
|
||||
this.setState( CKEDITOR.TRISTATE_OFF );
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.event.implementOn( CKEDITOR.command.prototype );
|
||||
|
||||
/**
|
||||
* Indicates the previous command state.
|
||||
*
|
||||
* alert( command.previousState );
|
||||
*
|
||||
* @property {Number} previousState
|
||||
* @see #state
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the command state changes.
|
||||
*
|
||||
* command.on( 'state', function() {
|
||||
* // Alerts the new state.
|
||||
* alert( this.state );
|
||||
* } );
|
||||
*
|
||||
* @event state
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event refresh
|
||||
* @todo
|
||||
*/
|
||||
139
lib/ckeditor4/core/commanddefinition.js
Executable file
139
lib/ckeditor4/core/commanddefinition.js
Executable file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the "virtual" {@link CKEDITOR.commandDefinition} class,
|
||||
* which contains the defintion of a command. This file is for
|
||||
* documentation purposes only.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Virtual class that illustrates the features of command objects to be
|
||||
* passed to the {@link CKEDITOR.editor#addCommand} function.
|
||||
*
|
||||
* @class CKEDITOR.commandDefinition
|
||||
* @abstract
|
||||
*/
|
||||
|
||||
/**
|
||||
* The function to be fired when the commend is executed.
|
||||
*
|
||||
* editorInstance.addCommand( 'sample', {
|
||||
* exec: function( editor ) {
|
||||
* alert( 'Executing a command for the editor name "' + editor.name + '"!' );
|
||||
* }
|
||||
* } );
|
||||
*
|
||||
* @method exec
|
||||
* @param {CKEDITOR.editor} editor The editor within which run the command.
|
||||
* @param {Object} [data] Additional data to be used to execute the command.
|
||||
* @returns {Boolean} Whether the command has been successfully executed.
|
||||
* Defaults to `true`, if nothing is returned.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether the command need to be hooked into the redo/undo system.
|
||||
*
|
||||
* editorInstance.addCommand( 'alertName', {
|
||||
* exec: function( editor ) {
|
||||
* alert( editor.name );
|
||||
* },
|
||||
* canUndo: false // No support for undo/redo.
|
||||
* } );
|
||||
*
|
||||
* @property {Boolean} [canUndo=true]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether the command is asynchronous, which means that the
|
||||
* {@link CKEDITOR.editor#event-afterCommandExec} event will be fired by the
|
||||
* command itself manually, and that the return value of this command is not to
|
||||
* be returned by the {@link #exec} function.
|
||||
*
|
||||
* editorInstance.addCommand( 'loadOptions', {
|
||||
* exec: function( editor ) {
|
||||
* // Asynchronous operation below.
|
||||
* CKEDITOR.ajax.loadXml( 'data.xml', function() {
|
||||
* editor.fire( 'afterCommandExec' );
|
||||
* } );
|
||||
* },
|
||||
* async: true // The command need some time to complete after exec function returns.
|
||||
* } );
|
||||
*
|
||||
* @property {Boolean} [async=false]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether the command should give focus to the editor before execution.
|
||||
*
|
||||
* editorInstance.addCommand( 'maximize', {
|
||||
* exec: function( editor ) {
|
||||
* // ...
|
||||
* },
|
||||
* editorFocus: false // The command doesn't require focusing the editing document.
|
||||
* } );
|
||||
*
|
||||
* @property {Boolean} [editorFocus=true]
|
||||
* @see CKEDITOR.command#editorFocus
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Whether the command state should be set to {@link CKEDITOR#TRISTATE_DISABLED} on startup.
|
||||
*
|
||||
* editorInstance.addCommand( 'unlink', {
|
||||
* exec: function( editor ) {
|
||||
* // ...
|
||||
* },
|
||||
* startDisabled: true // Command is unavailable until selection is inside a link.
|
||||
* } );
|
||||
*
|
||||
* @property {Boolean} [startDisabled=false]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that this command is sensible to the selection context.
|
||||
* If `true`, the {@link CKEDITOR.command#method-refresh} method will be
|
||||
* called for this command on selection changes, with a single parameter
|
||||
* representing the current elements path.
|
||||
*
|
||||
* @property {Boolean} [contextSensitive=true]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defined by command definition a function to determinate the command state, it will be invoked
|
||||
* when editor has it's `states` or `selection` changed.
|
||||
*
|
||||
* **Note:** The function provided must be calling {@link CKEDITOR.command#setState} in all circumstance,
|
||||
* if it is intended to update the command state.
|
||||
*
|
||||
* @method refresh
|
||||
* @param {CKEDITOR.editor} editor
|
||||
* @param {CKEDITOR.dom.elementPath} path
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the element name used to reflect the command state on selection changes.
|
||||
* If the selection is in a place where the element is not allowed, the command
|
||||
* will be disabled.
|
||||
* Setting this property overrides {@link #contextSensitive} to `true`.
|
||||
*
|
||||
* @property {Boolean} [context=true]
|
||||
*/
|
||||
|
||||
/**
|
||||
* The editor modes within which the command can be executed. The execution
|
||||
* will have no action if the current mode is not listed in this property.
|
||||
*
|
||||
* editorInstance.addCommand( 'link', {
|
||||
* exec: function( editor ) {
|
||||
* // ...
|
||||
* },
|
||||
* modes: { wysiwyg:1 } // Command is available in wysiwyg mode only.
|
||||
* } );
|
||||
*
|
||||
* @property {Object} [modes={ wysiwyg:1 }]
|
||||
* @see CKEDITOR.command#modes
|
||||
*/
|
||||
363
lib/ckeditor4/core/config.js
Executable file
363
lib/ckeditor4/core/config.js
Executable file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.config} object that stores the
|
||||
* default configuration settings.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used in conjunction with {@link CKEDITOR.config#enterMode}
|
||||
* and {@link CKEDITOR.config#shiftEnterMode} configuration
|
||||
* settings to make the editor produce `<p>` tags when
|
||||
* using the *Enter* key.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=1]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.ENTER_P = 1;
|
||||
|
||||
/**
|
||||
* Used in conjunction with {@link CKEDITOR.config#enterMode}
|
||||
* and {@link CKEDITOR.config#shiftEnterMode} configuration
|
||||
* settings to make the editor produce `<br>` tags when
|
||||
* using the *Enter* key.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=2]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.ENTER_BR = 2;
|
||||
|
||||
/**
|
||||
* Used in conjunction with {@link CKEDITOR.config#enterMode}
|
||||
* and {@link CKEDITOR.config#shiftEnterMode} configuration
|
||||
* settings to make the editor produce `<div>` tags when
|
||||
* using the *Enter* key.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=3]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.ENTER_DIV = 3;
|
||||
|
||||
/**
|
||||
* Stores default configuration settings. Changes to this object are
|
||||
* reflected in all editor instances, if not specified otherwise for a particular
|
||||
* instance.
|
||||
*
|
||||
* @class
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.config = {
|
||||
/**
|
||||
* The URL path for the custom configuration file to be loaded. If not
|
||||
* overloaded with inline configuration, it defaults to the `config.js`
|
||||
* file present in the root of the CKEditor installation directory.
|
||||
*
|
||||
* CKEditor will recursively load custom configuration files defined inside
|
||||
* other custom configuration files.
|
||||
*
|
||||
* // Load a specific configuration file.
|
||||
* CKEDITOR.replace( 'myfield', { customConfig: '/myconfig.js' } );
|
||||
*
|
||||
* // Do not load any custom configuration file.
|
||||
* CKEDITOR.replace( 'myfield', { customConfig: '' } );
|
||||
*
|
||||
* @cfg {String} [="<CKEditor folder>/config.js"]
|
||||
*/
|
||||
customConfig: 'config.js',
|
||||
|
||||
/**
|
||||
* Whether the replaced element (usually a `<textarea>`)
|
||||
* is to be updated automatically when posting the form containing the editor.
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
autoUpdateElement: true,
|
||||
|
||||
/**
|
||||
* The user interface language localization to use. If left empty, the editor
|
||||
* will automatically be localized to the user language. If the user language is not supported,
|
||||
* the language specified in the {@link CKEDITOR.config#defaultLanguage}
|
||||
* configuration setting is used.
|
||||
*
|
||||
* // Load the German interface.
|
||||
* config.language = 'de';
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
language: '',
|
||||
|
||||
/**
|
||||
* The language to be used if the {@link CKEDITOR.config#language}
|
||||
* setting is left empty and it is not possible to localize the editor to the user language.
|
||||
*
|
||||
* config.defaultLanguage = 'it';
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
defaultLanguage: 'en',
|
||||
|
||||
/**
|
||||
* The writting direction of the language used to write the editor
|
||||
* contents. Allowed values are:
|
||||
*
|
||||
* * `''` (empty string) - indicate content direction will be the same with either the editor
|
||||
* UI direction or page element direction depending on the creators:
|
||||
* * Themed UI: The same with user interface language direction;
|
||||
* * Inline: The same with the editable element text direction;
|
||||
* * `'ltr'` - for Left-To-Right language (like English);
|
||||
* * `'rtl'` - for Right-To-Left languages (like Arabic).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* config.contentsLangDirection = 'rtl';
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
contentsLangDirection: '',
|
||||
|
||||
/**
|
||||
* Sets the behavior of the *Enter* key. It also determines other behavior
|
||||
* rules of the editor, like whether the `<br>` element is to be used
|
||||
* as a paragraph separator when indenting text.
|
||||
* The allowed values are the following constants that cause the behavior outlined below:
|
||||
*
|
||||
* * {@link CKEDITOR#ENTER_P} (1) – new `<p>` paragraphs are created;
|
||||
* * {@link CKEDITOR#ENTER_BR} (2) – lines are broken with `<br>` elements;
|
||||
* * {@link CKEDITOR#ENTER_DIV} (3) – new `<div>` blocks are created.
|
||||
*
|
||||
* **Note**: It is recommended to use the {@link CKEDITOR#ENTER_P} setting because of
|
||||
* its semantic value and correctness. The editor is optimized for this setting.
|
||||
*
|
||||
* // Not recommended.
|
||||
* config.enterMode = CKEDITOR.ENTER_BR;
|
||||
*
|
||||
* @cfg {Number} [=CKEDITOR.ENTER_P]
|
||||
*/
|
||||
enterMode: CKEDITOR.ENTER_P,
|
||||
|
||||
/**
|
||||
* Force the use of {@link CKEDITOR.config#enterMode} as line break regardless
|
||||
* of the context. If, for example, {@link CKEDITOR.config#enterMode} is set
|
||||
* to {@link CKEDITOR#ENTER_P}, pressing the *Enter* key inside a
|
||||
* `<div>` element will create a new paragraph with `<p>`
|
||||
* instead of a `<div>`.
|
||||
*
|
||||
* // Not recommended.
|
||||
* config.forceEnterMode = true;
|
||||
*
|
||||
* @since 3.2.1
|
||||
* @cfg
|
||||
*/
|
||||
forceEnterMode: false,
|
||||
|
||||
/**
|
||||
* Similarly to the {@link CKEDITOR.config#enterMode} setting, it defines the behavior
|
||||
* of the *Shift+Enter* key combination.
|
||||
*
|
||||
* The allowed values are the following constants the behavior outlined below:
|
||||
*
|
||||
* * {@link CKEDITOR#ENTER_P} (1) – new `<p>` paragraphs are created;
|
||||
* * {@link CKEDITOR#ENTER_BR} (2) – lines are broken with `<br>` elements;
|
||||
* * {@link CKEDITOR#ENTER_DIV} (3) – new `<div>` blocks are created.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* config.shiftEnterMode = CKEDITOR.ENTER_P;
|
||||
*
|
||||
* @cfg {Number} [=CKEDITOR.ENTER_BR]
|
||||
*/
|
||||
shiftEnterMode: CKEDITOR.ENTER_BR,
|
||||
|
||||
/**
|
||||
* Sets the `DOCTYPE` to be used when loading the editor content as HTML.
|
||||
*
|
||||
* // Set the DOCTYPE to the HTML 4 (Quirks) mode.
|
||||
* config.docType = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">';
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
docType: '<!DOCTYPE html>',
|
||||
|
||||
/**
|
||||
* Sets the `id` attribute to be used on the `body` element
|
||||
* of the editing area. This can be useful when you intend to reuse the original CSS
|
||||
* file you are using on your live website and want to assign the editor the same ID
|
||||
* as the section that will include the contents. In this way ID-specific CSS rules will
|
||||
* be enabled.
|
||||
*
|
||||
* config.bodyId = 'contents_id';
|
||||
*
|
||||
* @since 3.1
|
||||
* @cfg
|
||||
*/
|
||||
bodyId: '',
|
||||
|
||||
/**
|
||||
* Sets the `class` attribute to be used on the `body` element
|
||||
* of the editing area. This can be useful when you intend to reuse the original CSS
|
||||
* file you are using on your live website and want to assign the editor the same class
|
||||
* as the section that will include the contents. In this way class-specific CSS rules will
|
||||
* be enabled.
|
||||
*
|
||||
* config.bodyClass = 'contents';
|
||||
*
|
||||
* @since 3.1
|
||||
* @cfg
|
||||
*/
|
||||
bodyClass: '',
|
||||
|
||||
/**
|
||||
* Indicates whether the contents to be edited are being input as a full HTML page.
|
||||
* A full page includes the `<html>`, `<head>`, and `<body>` elements.
|
||||
* The final output will also reflect this setting, including the
|
||||
* `<body>` contents only if this setting is disabled.
|
||||
*
|
||||
* config.fullPage = true;
|
||||
*
|
||||
* @since 3.1
|
||||
* @cfg
|
||||
*/
|
||||
fullPage: false,
|
||||
|
||||
/**
|
||||
* The height of the editing area (that includes the editor content). This
|
||||
* can be an integer, for pixel sizes, or any CSS-defined length unit.
|
||||
*
|
||||
* **Note:** Percent units (%) are not supported.
|
||||
*
|
||||
* config.height = 500; // 500 pixels.
|
||||
* config.height = '25em'; // CSS length.
|
||||
* config.height = '300px'; // CSS length.
|
||||
*
|
||||
* @cfg {Number/String}
|
||||
*/
|
||||
height: 200,
|
||||
|
||||
/**
|
||||
* Comma separated list of plugins to be used for an editor instance,
|
||||
* besides, the actual plugins that to be loaded could be still affected by two other settings:
|
||||
* {@link CKEDITOR.config#extraPlugins} and {@link CKEDITOR.config#removePlugins}.
|
||||
*
|
||||
* @cfg {String} [="<default list of plugins>"]
|
||||
*/
|
||||
plugins: '', // %REMOVE_LINE%
|
||||
|
||||
/**
|
||||
* A list of additional plugins to be loaded. This setting makes it easier
|
||||
* to add new plugins without having to touch {@link CKEDITOR.config#plugins} setting.
|
||||
*
|
||||
* config.extraPlugins = 'myplugin,anotherplugin';
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
extraPlugins: '',
|
||||
|
||||
/**
|
||||
* A list of plugins that must not be loaded. This setting makes it possible
|
||||
* to avoid loading some plugins defined in the {@link CKEDITOR.config#plugins}
|
||||
* setting, without having to touch it.
|
||||
*
|
||||
* **Note:** Plugin required by other plugin cannot be removed (error will be thrown).
|
||||
* So e.g. if `contextmenu` is required by `tabletools`, then it can be removed
|
||||
* only if `tabletools` isn't loaded.
|
||||
*
|
||||
* config.removePlugins = 'elementspath,save,font';
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
removePlugins: '',
|
||||
|
||||
/**
|
||||
* List of regular expressions to be executed on input HTML,
|
||||
* indicating HTML source code that when matched, must **not** be available in the WYSIWYG
|
||||
* mode for editing.
|
||||
*
|
||||
* config.protectedSource.push( /<\?[\s\S]*?\?>/g ); // PHP code
|
||||
* config.protectedSource.push( /<%[\s\S]*?%>/g ); // ASP code
|
||||
* config.protectedSource.push( /(<asp:[^\>]+>[\s|\S]*?<\/asp:[^\>]+>)|(<asp:[^\>]+\/>)/gi ); // ASP.Net code
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
protectedSource: [],
|
||||
|
||||
/**
|
||||
* The editor `tabindex` value.
|
||||
*
|
||||
* config.tabIndex = 1;
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
tabIndex: 0,
|
||||
|
||||
/**
|
||||
* The editor UI outer width. This can be an integer, for pixel sizes, or
|
||||
* any CSS-defined unit.
|
||||
*
|
||||
* Unlike the {@link CKEDITOR.config#height} setting, this
|
||||
* one will set the outer width of the entire editor UI, not for the
|
||||
* editing area only.
|
||||
*
|
||||
* config.width = 850; // 850 pixels wide.
|
||||
* config.width = '75%'; // CSS unit.
|
||||
*
|
||||
* @cfg {String/Number}
|
||||
*/
|
||||
width: '',
|
||||
|
||||
/**
|
||||
* The base Z-index for floating dialog windows and popups.
|
||||
*
|
||||
* config.baseFloatZIndex = 2000;
|
||||
*
|
||||
* @cfg
|
||||
*/
|
||||
baseFloatZIndex: 10000,
|
||||
|
||||
/**
|
||||
* The keystrokes that are blocked by default as the browser implementation
|
||||
* is buggy. These default keystrokes are handled by the editor.
|
||||
*
|
||||
* @cfg {Array} [=[ CKEDITOR.CTRL + 66, CKEDITOR.CTRL + 73, CKEDITOR.CTRL + 85 ] // CTRL+B,I,U]
|
||||
*/
|
||||
blockedKeystrokes: [
|
||||
CKEDITOR.CTRL + 66, // CTRL+B
|
||||
CKEDITOR.CTRL + 73, // CTRL+I
|
||||
CKEDITOR.CTRL + 85 // CTRL+U
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates that some of the editor features, like alignment and text
|
||||
* direction, should use the "computed value" of the feature to indicate its
|
||||
* on/off state instead of using the "real value".
|
||||
*
|
||||
* If enabled in a Left-To-Right written document, the "Left Justify"
|
||||
* alignment button will be shown as active, even if the alignment style is not
|
||||
* explicitly applied to the current paragraph in the editor.
|
||||
*
|
||||
* config.useComputedState = false;
|
||||
*
|
||||
* @since 3.4
|
||||
* @cfg {Boolean} [useComputedState=true]
|
||||
*/
|
||||
|
||||
/**
|
||||
* The base user interface color to be used by the editor. Not all skins are
|
||||
* compatible with this setting.
|
||||
*
|
||||
* // Using a color code.
|
||||
* config.uiColor = '#AADC6E';
|
||||
*
|
||||
* // Using an HTML color name.
|
||||
* config.uiColor = 'Gold';
|
||||
*
|
||||
* @cfg {String} uiColor
|
||||
*/
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.config )
|
||||
153
lib/ckeditor4/core/creators/inline.js
Executable file
153
lib/ckeditor4/core/creators/inline.js
Executable file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/** @class CKEDITOR */
|
||||
|
||||
/**
|
||||
* Turns a DOM element with `contenteditable` attribute set to `true` into a
|
||||
* CKEditor instance. Check {@link CKEDITOR.dtd#$editable} for the list of
|
||||
* allowed element names.
|
||||
*
|
||||
* <div contenteditable="true" id="content">...</div>
|
||||
* ...
|
||||
* CKEDITOR.inline( 'content' );
|
||||
*
|
||||
* It is also possible to create an inline editor from the `<textarea>` element.
|
||||
* If you do so, an additional `<div>` element with editable content will be created
|
||||
* directly after the `<textarea>` element and the `<textarea>` element will be hidden.
|
||||
*
|
||||
* @param {Object/String} element The DOM element or its ID.
|
||||
* @param {Object} [instanceConfig] The specific configurations to apply to this editor instance.
|
||||
* See {@link CKEDITOR.config}.
|
||||
* @returns {CKEDITOR.editor} The editor instance created.
|
||||
*/
|
||||
CKEDITOR.inline = function( element, instanceConfig ) {
|
||||
if ( !CKEDITOR.env.isCompatible )
|
||||
return null;
|
||||
|
||||
element = CKEDITOR.dom.element.get( element );
|
||||
|
||||
// Avoid multiple inline editor instances on the same element.
|
||||
if ( element.getEditor() )
|
||||
throw 'The editor instance "' + element.getEditor().name + '" is already attached to the provided element.';
|
||||
|
||||
var editor = new CKEDITOR.editor( instanceConfig, element, CKEDITOR.ELEMENT_MODE_INLINE ),
|
||||
textarea = element.is( 'textarea' ) ? element : null;
|
||||
|
||||
if ( textarea ) {
|
||||
editor.setData( textarea.getValue(), null, true );
|
||||
|
||||
//Change element from textarea to div
|
||||
element = CKEDITOR.dom.element.createFromHtml(
|
||||
'<div contenteditable="' + !!editor.readOnly + '" class="cke_textarea_inline">' +
|
||||
textarea.getValue() +
|
||||
'</div>',
|
||||
CKEDITOR.document );
|
||||
|
||||
element.insertAfter( textarea );
|
||||
textarea.hide();
|
||||
|
||||
// Attaching the concrete form.
|
||||
if ( textarea.$.form )
|
||||
editor._attachToForm();
|
||||
} else {
|
||||
// Initial editor data is simply loaded from the page element content to make
|
||||
// data retrieval possible immediately after the editor creation.
|
||||
editor.setData( element.getHtml(), null, true );
|
||||
}
|
||||
|
||||
// Once the editor is loaded, start the UI.
|
||||
editor.on( 'loaded', function() {
|
||||
editor.fire( 'uiReady' );
|
||||
|
||||
// Enable editing on the element.
|
||||
editor.editable( element );
|
||||
|
||||
// Editable itself is the outermost element.
|
||||
editor.container = element;
|
||||
|
||||
// Load and process editor data.
|
||||
editor.setData( editor.getData( 1 ) );
|
||||
|
||||
// Clean on startup.
|
||||
editor.resetDirty();
|
||||
|
||||
editor.fire( 'contentDom' );
|
||||
|
||||
// Inline editing defaults to "wysiwyg" mode, so plugins don't
|
||||
// need to make special handling for this "mode-less" environment.
|
||||
editor.mode = 'wysiwyg';
|
||||
editor.fire( 'mode' );
|
||||
|
||||
// The editor is completely loaded for interaction.
|
||||
editor.status = 'ready';
|
||||
editor.fireOnce( 'instanceReady' );
|
||||
CKEDITOR.fire( 'instanceReady', null, editor );
|
||||
|
||||
// give priority to plugins that relay on editor#loaded for bootstrapping.
|
||||
}, null, null, 10000 );
|
||||
|
||||
// Handle editor destroying.
|
||||
editor.on( 'destroy', function() {
|
||||
// Remove container from DOM if inline-textarea editor.
|
||||
// Show <textarea> back again.
|
||||
if ( textarea ) {
|
||||
editor.container.clearCustomData();
|
||||
editor.container.remove();
|
||||
textarea.show();
|
||||
}
|
||||
|
||||
editor.element.clearCustomData();
|
||||
|
||||
delete editor.element;
|
||||
} );
|
||||
|
||||
return editor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls {@link CKEDITOR#inline} for all page elements with
|
||||
* `contenteditable` attribute set to `true`.
|
||||
*
|
||||
*/
|
||||
CKEDITOR.inlineAll = function() {
|
||||
var el, data;
|
||||
|
||||
for ( var name in CKEDITOR.dtd.$editable ) {
|
||||
var elements = CKEDITOR.document.getElementsByTag( name );
|
||||
|
||||
for ( var i = 0, len = elements.count(); i < len; i++ ) {
|
||||
el = elements.getItem( i );
|
||||
|
||||
if ( el.getAttribute( 'contenteditable' ) == 'true' ) {
|
||||
// Fire the "inline" event, making it possible to customize
|
||||
// the instance settings and eventually cancel the creation.
|
||||
|
||||
data = {
|
||||
element: el,
|
||||
config: {}
|
||||
};
|
||||
|
||||
if ( CKEDITOR.fire( 'inline', data ) !== false )
|
||||
CKEDITOR.inline( el, data.config );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.domReady( function() {
|
||||
!CKEDITOR.disableAutoInline && CKEDITOR.inlineAll();
|
||||
} );
|
||||
})();
|
||||
|
||||
/**
|
||||
* Disables creating the inline editor automatically for elements with
|
||||
* `contenteditable` attribute set to the `true`.
|
||||
*
|
||||
* CKEDITOR.disableAutoInline = true;
|
||||
*
|
||||
* @cfg {Boolean} [disableAutoInline=false]
|
||||
*/
|
||||
452
lib/ckeditor4/core/creators/themedui.js
Executable file
452
lib/ckeditor4/core/creators/themedui.js
Executable file
@@ -0,0 +1,452 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/** @class CKEDITOR */
|
||||
|
||||
/**
|
||||
* The class name used to identify `<textarea>` elements to be replaced
|
||||
* by CKEditor instances. Set it to empty/`null` to disable this feature.
|
||||
*
|
||||
* CKEDITOR.replaceClass = 'rich_editor';
|
||||
*
|
||||
* @cfg {String} [replaceClass='ckeditor']
|
||||
*/
|
||||
CKEDITOR.replaceClass = 'ckeditor';
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <textarea id="myfield" name="myfield"></textarea>
|
||||
* ...
|
||||
* CKEDITOR.replace( 'myfield' );
|
||||
*
|
||||
* var textarea = document.body.appendChild( document.createElement( 'textarea' ) );
|
||||
* CKEDITOR.replace( textarea );
|
||||
*
|
||||
* @param {Object/String} element The DOM element (textarea), its ID, or name.
|
||||
* @param {Object} [config] The specific configuration to apply to this
|
||||
* editor instance. Configuration set here will override the global CKEditor settings
|
||||
* (see {@link CKEDITOR.config}).
|
||||
* @returns {CKEDITOR.editor} The editor instance created.
|
||||
*/
|
||||
CKEDITOR.replace = function( element, config ) {
|
||||
return createInstance( element, config, null, CKEDITOR.ELEMENT_MODE_REPLACE );
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new editor instance at the end of a specific DOM element.
|
||||
*
|
||||
* <div id="editorSpace"></div>
|
||||
* ...
|
||||
* CKEDITOR.appendTo( 'editorSpace' );
|
||||
*
|
||||
* @param {Object/String} element The DOM element, its ID, or name.
|
||||
* @param {Object} [config] The specific configuration to apply to this
|
||||
* editor instance. Configuration set here will override the global CKEditor settings
|
||||
* (see {@link CKEDITOR.config}).
|
||||
* @param {String} [data] Since 3.3. Initial value for the instance.
|
||||
* @returns {CKEDITOR.editor} The editor instance created.
|
||||
*/
|
||||
CKEDITOR.appendTo = function( element, config, data )
|
||||
{
|
||||
return createInstance( element, config, data, CKEDITOR.ELEMENT_MODE_APPENDTO );
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces all `<textarea>` elements available in the document with
|
||||
* editor instances.
|
||||
*
|
||||
* // Replace all <textarea> elements in the page.
|
||||
* CKEDITOR.replaceAll();
|
||||
*
|
||||
* // Replace all <textarea class="myClassName"> elements in the page.
|
||||
* CKEDITOR.replaceAll( 'myClassName' );
|
||||
*
|
||||
* // Selectively replace <textarea> elements, based on custom assertions.
|
||||
* CKEDITOR.replaceAll( function( textarea, config ) {
|
||||
* // Custom code to evaluate the replace, returning false
|
||||
* // if it must not be done.
|
||||
* // It also passes the "config" parameter, so the
|
||||
* // developer can customize the instance.
|
||||
* } );
|
||||
*/
|
||||
CKEDITOR.replaceAll = function() {
|
||||
var textareas = document.getElementsByTagName( 'textarea' );
|
||||
|
||||
for ( var i = 0; i < textareas.length; i++ ) {
|
||||
var config = null,
|
||||
textarea = textareas[ i ];
|
||||
|
||||
// The "name" and/or "id" attribute must exist.
|
||||
if ( !textarea.name && !textarea.id )
|
||||
continue;
|
||||
|
||||
if ( typeof arguments[ 0 ] == 'string' ) {
|
||||
// The textarea class name could be passed as the function
|
||||
// parameter.
|
||||
|
||||
var classRegex = new RegExp( '(?:^|\\s)' + arguments[ 0 ] + '(?:$|\\s)' );
|
||||
|
||||
if ( !classRegex.test( textarea.className ) )
|
||||
continue;
|
||||
} else if ( typeof arguments[ 0 ] == 'function' ) {
|
||||
// An assertion function could be passed as the function parameter.
|
||||
// It must explicitly return "false" to ignore a specific <textarea>.
|
||||
config = {};
|
||||
if ( arguments[ 0 ]( textarea, config ) === false )
|
||||
continue;
|
||||
}
|
||||
|
||||
this.replace( textarea, config );
|
||||
}
|
||||
};
|
||||
|
||||
/** @class CKEDITOR.editor */
|
||||
|
||||
/**
|
||||
* Registers an editing mode. This function is to be used mainly by plugins.
|
||||
*
|
||||
* @param {String} mode The mode name.
|
||||
* @param {Function} exec The function that performs the actual mode change.
|
||||
*/
|
||||
CKEDITOR.editor.prototype.addMode = function( mode, exec ) {
|
||||
( this._.modes || ( this._.modes = {} ) )[ mode ] = exec;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the editing mode of this editor instance.
|
||||
*
|
||||
* **Note:** The mode switch could be asynchronous depending on the mode provider.
|
||||
* Use the `callback` to hook subsequent code.
|
||||
*
|
||||
* // Switch to "source" view.
|
||||
* CKEDITOR.instances.editor1.setMode( 'source' );
|
||||
* // Switch to "wysiwyg" view and be notified on completion.
|
||||
* CKEDITOR.instances.editor1.setMode( 'wysiwyg', function() { alert( 'wysiwyg mode loaded!' ); } );
|
||||
*
|
||||
* @param {String} [newMode] If not specified, the {@link CKEDITOR.config#startupMode} will be used.
|
||||
* @param {Function} [callback] Optional callback function which is invoked once the mode switch has succeeded.
|
||||
*/
|
||||
CKEDITOR.editor.prototype.setMode = function( newMode, callback ) {
|
||||
var editor = this;
|
||||
|
||||
var modes = this._.modes;
|
||||
|
||||
// Mode loading quickly fails.
|
||||
if ( newMode == editor.mode || !modes || !modes[ newMode ] )
|
||||
return;
|
||||
|
||||
editor.fire( 'beforeSetMode', newMode );
|
||||
|
||||
if ( editor.mode ) {
|
||||
var isDirty = editor.checkDirty();
|
||||
|
||||
editor._.previousMode = editor.mode;
|
||||
|
||||
editor.fire( 'beforeModeUnload' );
|
||||
|
||||
// Detach the current editable.
|
||||
editor.editable( 0 );
|
||||
|
||||
// Clear up the mode space.
|
||||
editor.ui.space( 'contents' ).setHtml( '' );
|
||||
|
||||
editor.mode = '';
|
||||
}
|
||||
|
||||
// Fire the mode handler.
|
||||
this._.modes[ newMode ]( function() {
|
||||
// Set the current mode.
|
||||
editor.mode = newMode;
|
||||
|
||||
if ( isDirty !== undefined ) {
|
||||
!isDirty && editor.resetDirty();
|
||||
}
|
||||
|
||||
// Delay to avoid race conditions (setMode inside setMode).
|
||||
setTimeout( function() {
|
||||
editor.fire( 'mode' );
|
||||
callback && callback.call( editor );
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the editor interface.
|
||||
*
|
||||
* editor.resize( 900, 300 );
|
||||
*
|
||||
* editor.resize( '100%', 450, true );
|
||||
*
|
||||
* @param {Number/String} width The new width. It can be an integer denoting a value
|
||||
* in pixels or a CSS size value with unit.
|
||||
* @param {Number/String} height The new height. It can be an integer denoting a value
|
||||
* in pixels or a CSS size value with unit.
|
||||
* @param {Boolean} [isContentHeight] Indicates that the provided height is to
|
||||
* be applied to the editor content area, and not to the entire editor
|
||||
* interface. Defaults to `false`.
|
||||
* @param {Boolean} [resizeInner] Indicates that it is the inner interface
|
||||
* element that must be resized, not the outer element. The default theme
|
||||
* defines the editor interface inside a pair of `<span>` elements
|
||||
* (`<span><span>...</span></span>`). By default the first,
|
||||
* outer `<span>` element receives the sizes. If this parameter is set to
|
||||
* `true`, the second, inner `<span>` is resized instead.
|
||||
*/
|
||||
CKEDITOR.editor.prototype.resize = function( width, height, isContentHeight, resizeInner ) {
|
||||
var container = this.container,
|
||||
contents = this.ui.space( 'contents' ),
|
||||
contentsFrame = CKEDITOR.env.webkit && this.document && this.document.getWindow().$.frameElement,
|
||||
outer = resizeInner ? container.getChild( 1 ) : container;
|
||||
|
||||
// Set as border box width. (#5353)
|
||||
outer.setSize( 'width', width, true );
|
||||
|
||||
// WebKit needs to refresh the iframe size to avoid rendering issues. (1/2) (#8348)
|
||||
contentsFrame && ( contentsFrame.style.width = '1%' );
|
||||
|
||||
// Get the height delta between the outer table and the content area.
|
||||
// If we're setting the content area's height, then we don't need the delta.
|
||||
var delta = isContentHeight ? 0 : ( outer.$.offsetHeight || 0 ) - ( contents.$.clientHeight || 0 );
|
||||
contents.setStyle( 'height', Math.max( height - delta, 0 ) + 'px' );
|
||||
|
||||
// WebKit needs to refresh the iframe size to avoid rendering issues. (2/2) (#8348)
|
||||
contentsFrame && ( contentsFrame.style.width = '100%' );
|
||||
|
||||
// Emit a resize event.
|
||||
this.fire( 'resize' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the element that can be used to check the editor size. This method
|
||||
* is mainly used by the `resize` plugin, which adds a UI handle that can be used
|
||||
* to resize the editor.
|
||||
*
|
||||
* @param {Boolean} forContents Whether to return the "contents" part of the theme instead of the container.
|
||||
* @returns {CKEDITOR.dom.element} The resizable element.
|
||||
*/
|
||||
CKEDITOR.editor.prototype.getResizable = function( forContents ) {
|
||||
return forContents ? this.ui.space( 'contents' ) : this.container;
|
||||
};
|
||||
|
||||
function createInstance( element, config, data, mode ) {
|
||||
if ( !CKEDITOR.env.isCompatible )
|
||||
return null;
|
||||
|
||||
element = CKEDITOR.dom.element.get( element );
|
||||
|
||||
// Avoid multiple inline editor instances on the same element.
|
||||
if ( element.getEditor() )
|
||||
throw 'The editor instance "' + element.getEditor().name + '" is already attached to the provided element.';
|
||||
|
||||
// Create the editor instance.
|
||||
var editor = new CKEDITOR.editor( config, element, mode );
|
||||
|
||||
if ( mode == CKEDITOR.ELEMENT_MODE_REPLACE ) {
|
||||
// Do not replace the textarea right now, just hide it. The effective
|
||||
// replacement will be done later in the editor creation lifecycle.
|
||||
element.setStyle( 'visibility', 'hidden' );
|
||||
|
||||
// #8031 Remember if textarea was required and remove the attribute.
|
||||
editor._.required = element.hasAttribute( 'required' );
|
||||
element.removeAttribute( 'required' );
|
||||
}
|
||||
|
||||
data && editor.setData( data, null, true );
|
||||
|
||||
// Once the editor is loaded, start the UI.
|
||||
editor.on( 'loaded', function() {
|
||||
loadTheme( editor );
|
||||
|
||||
if ( mode == CKEDITOR.ELEMENT_MODE_REPLACE && editor.config.autoUpdateElement && element.$.form )
|
||||
editor._attachToForm();
|
||||
|
||||
editor.setMode( editor.config.startupMode, function() {
|
||||
// Clean on startup.
|
||||
editor.resetDirty();
|
||||
|
||||
// Editor is completely loaded for interaction.
|
||||
editor.status = 'ready';
|
||||
editor.fireOnce( 'instanceReady' );
|
||||
CKEDITOR.fire( 'instanceReady', null, editor );
|
||||
});
|
||||
});
|
||||
|
||||
editor.on( 'destroy', destroy );
|
||||
return editor;
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
var editor = this,
|
||||
container = editor.container,
|
||||
element = editor.element;
|
||||
|
||||
if ( container ) {
|
||||
container.clearCustomData();
|
||||
container.remove();
|
||||
}
|
||||
|
||||
if ( element ) {
|
||||
element.clearCustomData();
|
||||
if ( editor.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) {
|
||||
element.show();
|
||||
if ( editor._.required )
|
||||
element.setAttribute( 'required', 'required' );
|
||||
}
|
||||
delete editor.element;
|
||||
}
|
||||
}
|
||||
|
||||
var themedTpl;
|
||||
|
||||
function loadTheme( editor ) {
|
||||
var name = editor.name,
|
||||
element = editor.element,
|
||||
elementMode = editor.elementMode;
|
||||
|
||||
// Get the HTML for the predefined spaces.
|
||||
var topHtml = editor.fire( 'uiSpace', { space: 'top', html: '' } ).html;
|
||||
var bottomHtml = editor.fire( 'uiSpace', { space: 'bottom', html: '' } ).html;
|
||||
|
||||
if ( !themedTpl ) {
|
||||
themedTpl = CKEDITOR.addTemplate( 'maincontainer', '<{outerEl}' +
|
||||
' id="cke_{name}"' +
|
||||
' class="{id} cke cke_reset cke_chrome cke_editor_{name} cke_{langDir} ' + CKEDITOR.env.cssClass + '" ' +
|
||||
' dir="{langDir}"' +
|
||||
' lang="{langCode}"' +
|
||||
' role="application"' +
|
||||
' aria-labelledby="cke_{name}_arialbl">' +
|
||||
'<span id="cke_{name}_arialbl" class="cke_voice_label">{voiceLabel}</span>' +
|
||||
'<{outerEl} class="cke_inner cke_reset" role="presentation">' +
|
||||
'{topHtml}' +
|
||||
'<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation"></{outerEl}>' +
|
||||
'{bottomHtml}' +
|
||||
'</{outerEl}>' +
|
||||
'</{outerEl}>' );
|
||||
}
|
||||
|
||||
var container = CKEDITOR.dom.element.createFromHtml( themedTpl.output({
|
||||
id: editor.id,
|
||||
name: name,
|
||||
langDir: editor.lang.dir,
|
||||
langCode: editor.langCode,
|
||||
voiceLabel: [ editor.lang.editor, editor.name ].join( ', ' ),
|
||||
topHtml: topHtml ? '<span id="' + editor.ui.spaceId( 'top' ) + '" class="cke_top cke_reset_all" role="presentation" style="height:auto">' + topHtml + '</span>' : '',
|
||||
contentId: editor.ui.spaceId( 'contents' ),
|
||||
bottomHtml: bottomHtml ? '<span id="' + editor.ui.spaceId( 'bottom' ) + '" class="cke_bottom cke_reset_all" role="presentation">' + bottomHtml + '</span>' : '',
|
||||
outerEl: CKEDITOR.env.ie ? 'span' : 'div' // #9571
|
||||
}));
|
||||
|
||||
if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) {
|
||||
element.hide();
|
||||
container.insertAfter( element );
|
||||
} else
|
||||
element.append( container );
|
||||
|
||||
editor.container = container;
|
||||
|
||||
// Make top and bottom spaces unelectable, but not content space,
|
||||
// otherwise the editable area would be affected.
|
||||
topHtml && editor.ui.space( 'top' ).unselectable();
|
||||
bottomHtml && editor.ui.space( 'bottom' ).unselectable();
|
||||
|
||||
var width = editor.config.width, height = editor.config.height;
|
||||
if ( width )
|
||||
container.setStyle( 'width', CKEDITOR.tools.cssLength( width ) );
|
||||
|
||||
// The editor height is applied to the contents space.
|
||||
if ( height )
|
||||
editor.ui.space( 'contents' ).setStyle( 'height', CKEDITOR.tools.cssLength( height ) );
|
||||
|
||||
// Disable browser context menu for editor's chrome.
|
||||
container.disableContextMenu();
|
||||
|
||||
// Redirect the focus into editor for webkit. (#5713)
|
||||
CKEDITOR.env.webkit && container.on( 'focus', function() {
|
||||
editor.focus();
|
||||
});
|
||||
|
||||
editor.fireOnce( 'uiReady' );
|
||||
}
|
||||
|
||||
// Replace all textareas with the default class name.
|
||||
CKEDITOR.domReady( function() {
|
||||
CKEDITOR.replaceClass && CKEDITOR.replaceAll( CKEDITOR.replaceClass );
|
||||
});
|
||||
})();
|
||||
|
||||
/**
|
||||
* The current editing mode. An editing mode basically provides
|
||||
* different ways of editing or viewing the contents.
|
||||
*
|
||||
* alert( CKEDITOR.instances.editor1.mode ); // (e.g.) 'wysiwyg'
|
||||
*
|
||||
* @readonly
|
||||
* @property {String} mode
|
||||
*/
|
||||
|
||||
/**
|
||||
* The mode to load at the editor startup. It depends on the plugins
|
||||
* loaded. By default, the `wysiwyg` and `source` modes are available.
|
||||
*
|
||||
* config.startupMode = 'source';
|
||||
*
|
||||
* @cfg {String} [startupMode='wysiwyg']
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
CKEDITOR.config.startupMode = 'wysiwyg';
|
||||
|
||||
/**
|
||||
* Fired after the editor instance is resized through
|
||||
* the {@link CKEDITOR.editor#method-resize CKEDITOR.resize} method.
|
||||
*
|
||||
* @event resize
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired before changing the editing mode. See also
|
||||
* {@link #beforeSetMode} and {@link #event-mode}.
|
||||
*
|
||||
* @event beforeModeUnload
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired before the editor mode is set. See also
|
||||
* {@link #event-mode} and {@link #beforeModeUnload}.
|
||||
*
|
||||
* @since 3.5.3
|
||||
* @event beforeSetMode
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
* @param {String} data The name of the mode which is about to be set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after setting the editing mode. See also {@link #beforeSetMode} and {@link #beforeModeUnload}
|
||||
*
|
||||
* @event mode
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the editor (replacing a `<textarea>` which has a `required` attribute) is empty during form submission.
|
||||
*
|
||||
* This event replaces native required fields validation that the browsers cannot
|
||||
* perform when CKEditor replaces `<textarea>` elements.
|
||||
*
|
||||
* You can cancel this event to prevent the page from submitting data.
|
||||
*
|
||||
* editor.on( 'required', function( evt ) {
|
||||
* alert( 'Article content is required.' );
|
||||
* evt.cancel();
|
||||
* } );
|
||||
*
|
||||
* @event required
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
*/
|
||||
70
lib/ckeditor4/core/dataprocessor.js
Executable file
70
lib/ckeditor4/core/dataprocessor.js
Executable file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the "virtual" {@link CKEDITOR.dataProcessor} class, which
|
||||
* defines the basic structure of data processor objects to be
|
||||
* set to {@link CKEDITOR.editor.dataProcessor}.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If defined, points to the data processor which is responsible to translate
|
||||
* and transform the editor data on input and output.
|
||||
* Generaly it will point to an instance of {@link CKEDITOR.htmlDataProcessor},
|
||||
* which handles HTML data. The editor may also handle other data formats by
|
||||
* using different data processors provided by specific plugins.
|
||||
*
|
||||
* @property {CKEDITOR.dataProcessor} dataProcessor
|
||||
* @member CKEDITOR.editor
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a data processor, which is responsible to translate and
|
||||
* transform the editor data on input and output.
|
||||
*
|
||||
* This class is here for documentation purposes only and is not really part of
|
||||
* the API. It serves as the base ("interface") for data processors implementation.
|
||||
*
|
||||
* @class CKEDITOR.dataProcessor
|
||||
* @abstract
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms input data into HTML to be loaded in the editor.
|
||||
* While the editor is able to handle non HTML data (like BBCode), at runtime
|
||||
* it can handle HTML data only. The role of the data processor is transforming
|
||||
* the input data into HTML through this function.
|
||||
*
|
||||
* // Tranforming BBCode data, having a custom BBCode data processor.
|
||||
* var data = 'This is [b]an example[/b].';
|
||||
* var html = editor.dataProcessor.toHtml( data ); // '<p>This is <b>an example</b>.</p>'
|
||||
*
|
||||
* @method toHtml
|
||||
* @param {String} data The input data to be transformed.
|
||||
* @param {String} [fixForBody] The tag name to be used if the data must be
|
||||
* fixed because it is supposed to be loaded direcly into the `<body>`
|
||||
* tag. This is generally not used by non-HTML data processors.
|
||||
* @todo fixForBody type - compare to htmlDataProcessor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms HTML into data to be outputted by the editor, in the format
|
||||
* expected by the data processor.
|
||||
*
|
||||
* While the editor is able to handle non HTML data (like BBCode), at runtime
|
||||
* it can handle HTML data only. The role of the data processor is transforming
|
||||
* the HTML data containined by the editor into a specific data format through
|
||||
* this function.
|
||||
*
|
||||
* // Tranforming into BBCode data, having a custom BBCode data processor.
|
||||
* var html = '<p>This is <b>an example</b>.</p>';
|
||||
* var data = editor.dataProcessor.toDataFormat( html ); // 'This is [b]an example[/b].'
|
||||
*
|
||||
* @method toDataFormat
|
||||
* @param {String} html The HTML to be transformed.
|
||||
* @param {String} fixForBody The tag name to be used if the output data is
|
||||
* coming from `<body>` and may be eventually fixed for it. This is
|
||||
* generally not used by non-HTML data processors.
|
||||
*/
|
||||
13
lib/ckeditor4/core/dom.js
Executable file
13
lib/ckeditor4/core/dom.js
Executable file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.dom} object, which contains DOM
|
||||
* manipulation objects and function.
|
||||
*/
|
||||
|
||||
CKEDITOR.dom = {};
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.dom )
|
||||
53
lib/ckeditor4/core/dom/comment.js
Executable file
53
lib/ckeditor4/core/dom/comment.js
Executable file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.dom.comment} class, which represents
|
||||
* a DOM comment node.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a DOM comment node.
|
||||
*
|
||||
* var nativeNode = document.createComment( 'Example' );
|
||||
* var comment = new CKEDITOR.dom.comment( nativeNode );
|
||||
*
|
||||
* var comment = new CKEDITOR.dom.comment( 'Example' );
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.dom.node
|
||||
* @constructor Creates a comment class instance.
|
||||
* @param {Object/String} comment A native DOM comment node or a string containing
|
||||
* the text to use to create a new comment node.
|
||||
* @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
|
||||
* the node in case of new node creation. Defaults to the current document.
|
||||
*/
|
||||
CKEDITOR.dom.comment = function( comment, ownerDocument ) {
|
||||
if ( typeof comment == 'string' )
|
||||
comment = ( ownerDocument ? ownerDocument.$ : document ).createComment( comment );
|
||||
|
||||
CKEDITOR.dom.domObject.call( this, comment );
|
||||
};
|
||||
|
||||
CKEDITOR.dom.comment.prototype = new CKEDITOR.dom.node();
|
||||
|
||||
CKEDITOR.tools.extend( CKEDITOR.dom.comment.prototype, {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_COMMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_COMMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_COMMENT,
|
||||
|
||||
/**
|
||||
* Gets the outer HTML of this comment.
|
||||
*
|
||||
* @returns {String} The HTML `<!-- comment value -->`.
|
||||
*/
|
||||
getOuterHtml: function() {
|
||||
return '<!--' + this.$.nodeValue + '-->';
|
||||
}
|
||||
});
|
||||
270
lib/ckeditor4/core/dom/document.js
Executable file
270
lib/ckeditor4/core/dom/document.js
Executable file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.dom.document} class, which
|
||||
* represents a DOM document.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a DOM document.
|
||||
*
|
||||
* var document = new CKEDITOR.dom.document( document );
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.dom.domObject
|
||||
* @constructor Creates a document class instance.
|
||||
* @param {Object} domDocument A native DOM document.
|
||||
*/
|
||||
CKEDITOR.dom.document = function( domDocument ) {
|
||||
CKEDITOR.dom.domObject.call( this, domDocument );
|
||||
};
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.dom.document )
|
||||
|
||||
CKEDITOR.dom.document.prototype = new CKEDITOR.dom.domObject();
|
||||
|
||||
CKEDITOR.tools.extend( CKEDITOR.dom.document.prototype, {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_DOCUMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_DOCUMENT,
|
||||
|
||||
/**
|
||||
* Appends a CSS file to the document.
|
||||
*
|
||||
* CKEDITOR.document.appendStyleSheet( '/mystyles.css' );
|
||||
*
|
||||
* @param {String} cssFileUrl The CSS file URL.
|
||||
*/
|
||||
appendStyleSheet: function( cssFileUrl ) {
|
||||
if ( this.$.createStyleSheet )
|
||||
this.$.createStyleSheet( cssFileUrl );
|
||||
else {
|
||||
var link = new CKEDITOR.dom.element( 'link' );
|
||||
link.setAttributes({
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
href: cssFileUrl
|
||||
});
|
||||
|
||||
this.getHead().append( link );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a CSS style sheet and inserts it into the document.
|
||||
*
|
||||
* @param cssStyleText {String} CSS style text.
|
||||
* @returns {Object} The created DOM native style sheet object.
|
||||
*/
|
||||
appendStyleText: function( cssStyleText ) {
|
||||
if ( this.$.createStyleSheet ) {
|
||||
var styleSheet = this.$.createStyleSheet( "" );
|
||||
styleSheet.cssText = cssStyleText;
|
||||
} else {
|
||||
var style = new CKEDITOR.dom.element( 'style', this );
|
||||
style.append( new CKEDITOR.dom.text( cssStyleText, this ) );
|
||||
this.getHead().append( style );
|
||||
}
|
||||
|
||||
return styleSheet || style.$.sheet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates {@link CKEDITOR.dom.element} instance in this document.
|
||||
*
|
||||
* @returns {CKEDITOR.dom.element}
|
||||
* @todo
|
||||
*/
|
||||
createElement: function( name, attribsAndStyles ) {
|
||||
var element = new CKEDITOR.dom.element( name, this );
|
||||
|
||||
if ( attribsAndStyles ) {
|
||||
if ( attribsAndStyles.attributes )
|
||||
element.setAttributes( attribsAndStyles.attributes );
|
||||
|
||||
if ( attribsAndStyles.styles )
|
||||
element.setStyles( attribsAndStyles.styles );
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates {@link CKEDITOR.dom.text} instance in this document.
|
||||
*
|
||||
* @param {String} text Value of the text node.
|
||||
* @returns {CKEDITOR.dom.element}
|
||||
*/
|
||||
createText: function( text ) {
|
||||
return new CKEDITOR.dom.text( text, this );
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves the selection focus to this document's window.
|
||||
*/
|
||||
focus: function() {
|
||||
this.getWindow().focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the element that is currently designated as the active element in the document.
|
||||
*
|
||||
* **Note:** Only one element can be active at a time in a document.
|
||||
* An active element does not necessarily have focus,
|
||||
* but an element with focus is always the active element in a document.
|
||||
*
|
||||
* @returns {CKEDITOR.dom.element}
|
||||
*/
|
||||
getActive: function() {
|
||||
return new CKEDITOR.dom.element( this.$.activeElement );
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an element based on its id.
|
||||
*
|
||||
* var element = CKEDITOR.document.getById( 'myElement' );
|
||||
* alert( element.getId() ); // 'myElement'
|
||||
*
|
||||
* @param {String} elementId The element id.
|
||||
* @returns {CKEDITOR.dom.element} The element instance, or null if not found.
|
||||
*/
|
||||
getById: function( elementId ) {
|
||||
var $ = this.$.getElementById( elementId );
|
||||
return $ ? new CKEDITOR.dom.element( $ ) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a node based on its address. See {@link CKEDITOR.dom.node#getAddress}.
|
||||
*
|
||||
* @param {Array} address
|
||||
* @param {Boolean} [normalized=false]
|
||||
*/
|
||||
getByAddress: function( address, normalized ) {
|
||||
var $ = this.$.documentElement;
|
||||
|
||||
for ( var i = 0; $ && i < address.length; i++ ) {
|
||||
var target = address[ i ];
|
||||
|
||||
if ( !normalized ) {
|
||||
$ = $.childNodes[ target ];
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentIndex = -1;
|
||||
|
||||
for ( var j = 0; j < $.childNodes.length; j++ ) {
|
||||
var candidate = $.childNodes[ j ];
|
||||
|
||||
if ( normalized === true && candidate.nodeType == 3 && candidate.previousSibling && candidate.previousSibling.nodeType == 3 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
if ( currentIndex == target ) {
|
||||
$ = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ ? new CKEDITOR.dom.node( $ ) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets elements list based on given tag name.
|
||||
*
|
||||
* @param {String} tagName The element tag name.
|
||||
* @returns {CKEDITOR.dom.nodeList} The nodes list.
|
||||
*/
|
||||
getElementsByTag: function( tagName, namespace ) {
|
||||
if ( !( CKEDITOR.env.ie && !( document.documentMode > 8 ) ) && namespace )
|
||||
tagName = namespace + ':' + tagName;
|
||||
return new CKEDITOR.dom.nodeList( this.$.getElementsByTagName( tagName ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the `<head>` element for this document.
|
||||
*
|
||||
* var element = CKEDITOR.document.getHead();
|
||||
* alert( element.getName() ); // 'head'
|
||||
*
|
||||
* @returns {CKEDITOR.dom.element} The `<head>` element.
|
||||
*/
|
||||
getHead: function() {
|
||||
var head = this.$.getElementsByTagName( 'head' )[ 0 ];
|
||||
if ( !head )
|
||||
head = this.getDocumentElement().append( new CKEDITOR.dom.element( 'head' ), true );
|
||||
else
|
||||
head = new CKEDITOR.dom.element( head );
|
||||
|
||||
return head;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the `<body>` element for this document.
|
||||
*
|
||||
* var element = CKEDITOR.document.getBody();
|
||||
* alert( element.getName() ); // 'body'
|
||||
*
|
||||
* @returns {CKEDITOR.dom.element} The `<body>` element.
|
||||
*/
|
||||
getBody: function() {
|
||||
return new CKEDITOR.dom.element( this.$.body );
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the DOM document element for this document.
|
||||
*
|
||||
* @returns {CKEDITOR.dom.element} The DOM document element.
|
||||
*/
|
||||
getDocumentElement: function() {
|
||||
return new CKEDITOR.dom.element( this.$.documentElement );
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the window object that holds this document.
|
||||
*
|
||||
* @returns {CKEDITOR.dom.window} The window object.
|
||||
*/
|
||||
getWindow: function() {
|
||||
return new CKEDITOR.dom.window( this.$.parentWindow || this.$.defaultView );
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines the document contents through document.write. Note that the
|
||||
* previous document contents will be lost (cleaned).
|
||||
*
|
||||
* document.write(
|
||||
* '<html>' +
|
||||
* '<head><title>Sample Doc</title></head>' +
|
||||
* '<body>Document contents created by code</body>' +
|
||||
* '</html>'
|
||||
* );
|
||||
*
|
||||
* @since 3.5
|
||||
* @param {String} html The HTML defining the document contents.
|
||||
*/
|
||||
write: function( html ) {
|
||||
// Don't leave any history log in IE. (#5657)
|
||||
this.$.open( 'text/html', 'replace' );
|
||||
|
||||
// Support for custom document.domain in IE.
|
||||
//
|
||||
// The script must be appended because if placed before the
|
||||
// doctype, IE will go into quirks mode and mess with
|
||||
// the editable, e.g. by changing its default height.
|
||||
if ( CKEDITOR.env.ie )
|
||||
html = html.replace( /(?:^\s*<!DOCTYPE[^>]*?>)|^/i, '$&\n<script data-cke-temp="1">(' + CKEDITOR.tools.fixDomain + ')();</script>' );
|
||||
|
||||
this.$.write( html );
|
||||
this.$.close();
|
||||
}
|
||||
});
|
||||
45
lib/ckeditor4/core/dom/documentfragment.js
Executable file
45
lib/ckeditor4/core/dom/documentfragment.js
Executable file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* DocumentFragment is a "lightweight" or "minimal" Document object. It is
|
||||
* commonly used to extract a portion of a document's tree or to create a new
|
||||
* fragment of a document. Various operations may take DocumentFragment objects
|
||||
* as arguments and results in all the child nodes of the DocumentFragment being
|
||||
* moved to the child list of this node.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a document fragment class instance.
|
||||
* @param {Object} nodeOrDoc
|
||||
* @todo example and param doc
|
||||
*/
|
||||
CKEDITOR.dom.documentFragment = function( nodeOrDoc ) {
|
||||
nodeOrDoc = nodeOrDoc || CKEDITOR.document;
|
||||
|
||||
if ( nodeOrDoc.type == CKEDITOR.NODE_DOCUMENT )
|
||||
this.$ = nodeOrDoc.$.createDocumentFragment();
|
||||
else
|
||||
this.$ = nodeOrDoc;
|
||||
};
|
||||
|
||||
CKEDITOR.tools.extend( CKEDITOR.dom.documentFragment.prototype, CKEDITOR.dom.element.prototype, {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_DOCUMENT_FRAGMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_DOCUMENT_FRAGMENT,
|
||||
|
||||
/**
|
||||
* Inserts document fragment's contents after specified node.
|
||||
*
|
||||
* @param {CKEDITOR.dom.node} node
|
||||
*/
|
||||
insertAfterNode: function( node ) {
|
||||
node = node.$;
|
||||
node.parentNode.insertBefore( this.$, node.nextSibling );
|
||||
}
|
||||
}, true, { 'append':1,'appendBogus':1,'getFirst':1,'getLast':1,'getParent':1,'getNext':1,'getPrevious':1,'appendTo':1,'moveChildren':1,'insertBefore':1,'insertAfterNode':1,'replace':1,'trim':1,'type':1,'ltrim':1,'rtrim':1,'getDocument':1,'getChildCount':1,'getChild':1,'getChildren':1 } );
|
||||
258
lib/ckeditor4/core/dom/domobject.js
Executable file
258
lib/ckeditor4/core/dom/domobject.js
Executable file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.editor} class, which is the base
|
||||
* for other classes representing DOM objects.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a DOM object. This class is not intended to be used directly. It
|
||||
* serves as the base class for other classes representing specific DOM
|
||||
* objects.
|
||||
*
|
||||
* @class
|
||||
* @mixins CKEDITOR.event
|
||||
* @constructor Creates a domObject class instance.
|
||||
* @param {Object} nativeDomObject A native DOM object.
|
||||
*/
|
||||
CKEDITOR.dom.domObject = function( nativeDomObject ) {
|
||||
if ( nativeDomObject ) {
|
||||
/**
|
||||
* The native DOM object represented by this class instance.
|
||||
*
|
||||
* var element = new CKEDITOR.dom.element( 'span' );
|
||||
* alert( element.$.nodeType ); // '1'
|
||||
*
|
||||
* @readonly
|
||||
* @property {Object}
|
||||
*/
|
||||
this.$ = nativeDomObject;
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.dom.domObject.prototype = (function() {
|
||||
// Do not define other local variables here. We want to keep the native
|
||||
// listener closures as clean as possible.
|
||||
|
||||
var getNativeListener = function( domObject, eventName ) {
|
||||
return function( domEvent ) {
|
||||
// In FF, when reloading the page with the editor focused, it may
|
||||
// throw an error because the CKEDITOR global is not anymore
|
||||
// available. So, we check it here first. (#2923)
|
||||
if ( typeof CKEDITOR != 'undefined' )
|
||||
domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) );
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Get the private `_` object which is bound to the native
|
||||
* DOM object using {@link #getCustomData}.
|
||||
*
|
||||
* var elementA = new CKEDITOR.dom.element( nativeElement );
|
||||
* elementA.getPrivate().value = 1;
|
||||
* ...
|
||||
* var elementB = new CKEDITOR.dom.element( nativeElement );
|
||||
* elementB.getPrivate().value; // 1
|
||||
*
|
||||
* @returns {Object} The private object.
|
||||
*/
|
||||
getPrivate: function() {
|
||||
var priv;
|
||||
|
||||
// Get the main private object from the custom data. Create it if not defined.
|
||||
if ( !( priv = this.getCustomData( '_' ) ) )
|
||||
this.setCustomData( '_', ( priv = {} ) );
|
||||
|
||||
return priv;
|
||||
},
|
||||
|
||||
// Docs inherited from event.
|
||||
on: function( eventName ) {
|
||||
// We customize the "on" function here. The basic idea is that we'll have
|
||||
// only one listener for a native event, which will then call all listeners
|
||||
// set to the event.
|
||||
|
||||
// Get the listeners holder object.
|
||||
var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
|
||||
|
||||
if ( !nativeListeners ) {
|
||||
nativeListeners = {};
|
||||
this.setCustomData( '_cke_nativeListeners', nativeListeners );
|
||||
}
|
||||
|
||||
// Check if we have a listener for that event.
|
||||
if ( !nativeListeners[ eventName ] ) {
|
||||
var listener = nativeListeners[ eventName ] = getNativeListener( this, eventName );
|
||||
|
||||
if ( this.$.addEventListener )
|
||||
this.$.addEventListener( eventName, listener, !!CKEDITOR.event.useCapture );
|
||||
else if ( this.$.attachEvent )
|
||||
this.$.attachEvent( 'on' + eventName, listener );
|
||||
}
|
||||
|
||||
// Call the original implementation.
|
||||
return CKEDITOR.event.prototype.on.apply( this, arguments );
|
||||
},
|
||||
|
||||
// Docs inherited from event.
|
||||
removeListener: function( eventName ) {
|
||||
// Call the original implementation.
|
||||
CKEDITOR.event.prototype.removeListener.apply( this, arguments );
|
||||
|
||||
// If we don't have listeners for this event, clean the DOM up.
|
||||
if ( !this.hasListeners( eventName ) ) {
|
||||
var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
|
||||
var listener = nativeListeners && nativeListeners[ eventName ];
|
||||
if ( listener ) {
|
||||
if ( this.$.removeEventListener )
|
||||
this.$.removeEventListener( eventName, listener, false );
|
||||
else if ( this.$.detachEvent )
|
||||
this.$.detachEvent( 'on' + eventName, listener );
|
||||
|
||||
delete nativeListeners[ eventName ];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes any listener set on this object.
|
||||
*
|
||||
* To avoid memory leaks we must assure that there are no
|
||||
* references left after the object is no longer needed.
|
||||
*/
|
||||
removeAllListeners: function() {
|
||||
var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
|
||||
for ( var eventName in nativeListeners ) {
|
||||
var listener = nativeListeners[ eventName ];
|
||||
if ( this.$.detachEvent )
|
||||
this.$.detachEvent( 'on' + eventName, listener );
|
||||
else if ( this.$.removeEventListener )
|
||||
this.$.removeEventListener( eventName, listener, false );
|
||||
|
||||
delete nativeListeners[ eventName ];
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(function( domObjectProto ) {
|
||||
var customData = {};
|
||||
|
||||
CKEDITOR.on( 'reset', function() {
|
||||
customData = {};
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines whether the specified object is equal to the current object.
|
||||
*
|
||||
* var doc = new CKEDITOR.dom.document( document );
|
||||
* alert( doc.equals( CKEDITOR.document ) ); // true
|
||||
* alert( doc == CKEDITOR.document ); // false
|
||||
*
|
||||
* @param {Object} object The object to compare with the current object.
|
||||
* @returns {Boolean} `true` if the object is equal.
|
||||
*/
|
||||
domObjectProto.equals = function( object ) {
|
||||
// Try/Catch to avoid IE permission error when object is from different document.
|
||||
try {
|
||||
return ( object && object.$ === this.$ );
|
||||
} catch ( er ) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a data slot value for this object. These values are shared by all
|
||||
* instances pointing to that same DOM object.
|
||||
*
|
||||
* **Note:** The created data slot is only guarantied to be available on this unique dom node,
|
||||
* thus any wish to continue access it from other element clones (either created by
|
||||
* clone node or from `innerHtml`) will fail, for such usage, please use
|
||||
* {@link CKEDITOR.dom.element#setAttribute} instead.
|
||||
*
|
||||
* var element = new CKEDITOR.dom.element( 'span' );
|
||||
* element.setCustomData( 'hasCustomData', true );
|
||||
*
|
||||
* @param {String} key A key used to identify the data slot.
|
||||
* @param {Object} value The value to set to the data slot.
|
||||
* @returns {CKEDITOR.dom.domObject} This DOM object instance.
|
||||
* @chainable
|
||||
*/
|
||||
domObjectProto.setCustomData = function( key, value ) {
|
||||
var expandoNumber = this.getUniqueId(),
|
||||
dataSlot = customData[ expandoNumber ] || ( customData[ expandoNumber ] = {} );
|
||||
|
||||
dataSlot[ key ] = value;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the value set to a data slot in this object.
|
||||
*
|
||||
* var element = new CKEDITOR.dom.element( 'span' );
|
||||
* alert( element.getCustomData( 'hasCustomData' ) ); // e.g. 'true'
|
||||
* alert( element.getCustomData( 'nonExistingKey' ) ); // null
|
||||
*
|
||||
* @param {String} key The key used to identify the data slot.
|
||||
* @returns {Object} This value set to the data slot.
|
||||
*/
|
||||
domObjectProto.getCustomData = function( key ) {
|
||||
var expandoNumber = this.$[ 'data-cke-expando' ],
|
||||
dataSlot = expandoNumber && customData[ expandoNumber ];
|
||||
|
||||
return ( dataSlot && key in dataSlot ) ? dataSlot[ key ] : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the value in data slot under given `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @returns {Object} Removed value or `null` if not found.
|
||||
*/
|
||||
domObjectProto.removeCustomData = function( key ) {
|
||||
var expandoNumber = this.$[ 'data-cke-expando' ],
|
||||
dataSlot = expandoNumber && customData[ expandoNumber ],
|
||||
retval, hadKey;
|
||||
|
||||
if ( dataSlot ) {
|
||||
retval = dataSlot[ key ];
|
||||
hadKey = key in dataSlot;
|
||||
delete dataSlot[ key ];
|
||||
}
|
||||
|
||||
return hadKey ? retval : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes any data stored on this object.
|
||||
* To avoid memory leaks we must assure that there are no
|
||||
* references left after the object is no longer needed.
|
||||
*/
|
||||
domObjectProto.clearCustomData = function() {
|
||||
// Clear all event listeners
|
||||
this.removeAllListeners();
|
||||
|
||||
var expandoNumber = this.$[ 'data-cke-expando' ];
|
||||
expandoNumber && delete customData[ expandoNumber ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets an ID that can be used to identiquely identify this DOM object in
|
||||
* the running session.
|
||||
*
|
||||
* @returns {Number} A unique ID.
|
||||
*/
|
||||
domObjectProto.getUniqueId = function() {
|
||||
return this.$[ 'data-cke-expando' ] || ( this.$[ 'data-cke-expando' ] = CKEDITOR.tools.getNextNumber() );
|
||||
};
|
||||
|
||||
// Implement CKEDITOR.event.
|
||||
CKEDITOR.event.implementOn( domObjectProto );
|
||||
|
||||
})( CKEDITOR.dom.domObject.prototype );
|
||||
1869
lib/ckeditor4/core/dom/element.js
Executable file
1869
lib/ckeditor4/core/dom/element.js
Executable file
File diff suppressed because it is too large
Load Diff
222
lib/ckeditor4/core/dom/elementpath.js
Executable file
222
lib/ckeditor4/core/dom/elementpath.js
Executable file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
// Elements that are considered the "Block limit" in an element path.
|
||||
var pathBlockLimitElements = {};
|
||||
for ( var tag in CKEDITOR.dtd.$blockLimit ) {
|
||||
// Exclude from list roots.
|
||||
if ( !( tag in CKEDITOR.dtd.$list ) )
|
||||
pathBlockLimitElements[ tag ] = 1;
|
||||
}
|
||||
|
||||
// Elements that are considered the "End level Block" in an element path.
|
||||
var pathBlockElements = {};
|
||||
|
||||
for ( tag in CKEDITOR.dtd.$block ) {
|
||||
// Exclude block limits, and empty block element, e.g. hr.
|
||||
if ( !( tag in CKEDITOR.dtd.$blockLimit || tag in CKEDITOR.dtd.$empty ) )
|
||||
pathBlockElements[ tag ] = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the list of nodes walked from the start node up to the editable element of the editor.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a element path class instance.
|
||||
* @param {CKEDITOR.dom.element} startNode From which the path should start.
|
||||
* @param {CKEDITOR.dom.element} root To which element the path should stop, default to the body element.
|
||||
*/
|
||||
CKEDITOR.dom.elementPath = function( startNode, root ) {
|
||||
var block = null;
|
||||
var blockLimit = null;
|
||||
var elements = [];
|
||||
|
||||
// Backward compact.
|
||||
root = root || startNode.getDocument().getBody();
|
||||
|
||||
var e = startNode;
|
||||
|
||||
do {
|
||||
if ( e.type == CKEDITOR.NODE_ELEMENT ) {
|
||||
elements.push( e );
|
||||
|
||||
if ( !this.lastElement ) {
|
||||
this.lastElement = e;
|
||||
|
||||
// If a table is fully selected at the end of the element path,
|
||||
// it must not become the block limit.
|
||||
if ( e.is( CKEDITOR.dtd.$object ) )
|
||||
continue;
|
||||
}
|
||||
|
||||
var elementName = e.getName();
|
||||
|
||||
if ( !blockLimit ) {
|
||||
if ( !block && pathBlockElements[ elementName ] )
|
||||
block = e;
|
||||
|
||||
if ( pathBlockLimitElements[ elementName ] ) {
|
||||
// End level DIV is considered as the block, if no block is available. (#525)
|
||||
// But it must NOT be as the root element.
|
||||
if ( !block && elementName == 'div' && !checkHasBlock( e ) && !e.equals( root ) ) {
|
||||
block = e;
|
||||
} else
|
||||
blockLimit = e;
|
||||
}
|
||||
}
|
||||
|
||||
if ( e.equals( root ) )
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ( ( e = e.getParent() ) );
|
||||
|
||||
/**
|
||||
* @property {CKEDITOR.dom.element}
|
||||
* @todo
|
||||
*/
|
||||
this.block = block;
|
||||
|
||||
/**
|
||||
* @property {CKEDITOR.dom.element}
|
||||
* @todo
|
||||
*/
|
||||
this.blockLimit = blockLimit;
|
||||
|
||||
/**
|
||||
* The root of the elements path - `startNode` argument passed to class constructor or body element.
|
||||
*
|
||||
* @property {CKEDITOR.dom.element}
|
||||
* @todo
|
||||
*/
|
||||
this.root = root;
|
||||
|
||||
/**
|
||||
* @property {CKEDITOR.dom.element[]}
|
||||
* @todo
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Search the path elements that meets the specified criteria.
|
||||
*
|
||||
* @param {String/Array/Function/Object/CKEDITOR.dom.element} query The criteria that can be
|
||||
* either a tag name, list (array and object) of tag names, element or an node evaluator function.
|
||||
* @param {Boolean} excludeRoot Not taking path root element into consideration.
|
||||
* @param {Boolean} fromTop Search start from the topmost element instead of bottom.
|
||||
* @returns {CKEDITOR.dom.element} The first matched dom element or `null`.
|
||||
*/
|
||||
contains: function( query, excludeRoot, fromTop ) {
|
||||
var evaluator;
|
||||
if ( typeof query == 'string' )
|
||||
evaluator = function( node ) {
|
||||
return node.getName() == query;
|
||||
};
|
||||
if ( query instanceof CKEDITOR.dom.element )
|
||||
evaluator = function( node ) {
|
||||
return node.equals( query );
|
||||
};
|
||||
else if ( CKEDITOR.tools.isArray( query ) )
|
||||
evaluator = function( node ) {
|
||||
return CKEDITOR.tools.indexOf( query, node.getName() ) > -1;
|
||||
};
|
||||
else if ( typeof query == 'function' )
|
||||
evaluator = query;
|
||||
else if ( typeof query == 'object' )
|
||||
evaluator = function( node ) {
|
||||
return node.getName() in query;
|
||||
};
|
||||
|
||||
var elements = this.elements,
|
||||
length = elements.length;
|
||||
excludeRoot && length--;
|
||||
|
||||
if ( fromTop ) {
|
||||
elements = Array.prototype.slice.call( elements, 0 );
|
||||
elements.reverse();
|
||||
}
|
||||
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
if ( evaluator( elements[ i ] ) )
|
||||
return elements[ i ];
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether the elements path is the proper context for the specified
|
||||
* tag name in the DTD.
|
||||
*
|
||||
* @param {String} tag The tag name.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isContextFor: function( tag ) {
|
||||
var holder;
|
||||
|
||||
// Check for block context.
|
||||
if ( tag in CKEDITOR.dtd.$block ) {
|
||||
// Indeterminate elements which are not subjected to be splitted or surrounded must be checked first.
|
||||
var inter = this.contains( CKEDITOR.dtd.$intermediate );
|
||||
holder = inter || ( this.root.equals( this.block ) && this.block ) || this.blockLimit;
|
||||
return !!holder.getDtd()[ tag ];
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the text direction for this elements path.
|
||||
*
|
||||
* @returns {'ltr'/'rtl'}
|
||||
*/
|
||||
direction: function() {
|
||||
var directionNode = this.block || this.blockLimit || this.root;
|
||||
return directionNode.getDirection( 1 );
|
||||
}
|
||||
};
|
||||
208
lib/ckeditor4/core/dom/event.js
Executable file
208
lib/ckeditor4/core/dom/event.js
Executable file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates an event class instance.
|
||||
* @param {Object} domEvent A native DOM event object.
|
||||
*/
|
||||
CKEDITOR.dom.event = function( domEvent ) {
|
||||
/**
|
||||
* The native DOM event object represented by this class instance.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
this.$ = domEvent;
|
||||
};
|
||||
|
||||
CKEDITOR.dom.event.prototype = {
|
||||
/**
|
||||
* Gets the key code associated to the event.
|
||||
*
|
||||
* alert( event.getKey() ); // '65' is 'a' has been pressed
|
||||
*
|
||||
* @returns {Number} The key code.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @returns {Number} The number representing the keys combination.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* 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();
|
||||
* } );
|
||||
*
|
||||
* @param {Boolean} [stopPropagation=false] Stop propagating this event in the
|
||||
* event chain.
|
||||
*/
|
||||
preventDefault: function( stopPropagation ) {
|
||||
var $ = this.$;
|
||||
if ( $.preventDefault )
|
||||
$.preventDefault();
|
||||
else
|
||||
$.returnValue = false;
|
||||
|
||||
if ( stopPropagation )
|
||||
this.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops this event propagation in the event chain.
|
||||
*/
|
||||
stopPropagation: function() {
|
||||
var $ = this.$;
|
||||
if ( $.stopPropagation )
|
||||
$.stopPropagation();
|
||||
else
|
||||
$.cancelBubble = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the DOM node where the event was targeted to.
|
||||
*
|
||||
* 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' );
|
||||
* } );
|
||||
*
|
||||
* @returns {CKEDITOR.dom.node} The target DOM node.
|
||||
*/
|
||||
getTarget: function() {
|
||||
var rawNode = this.$.target || this.$.srcElement;
|
||||
return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an integer value that indicates the current processing phase of an event.
|
||||
* For browsers that doesn't support event phase, {@link CKEDITOR#EVENT_PHASE_AT_TARGET} is always returned.
|
||||
*
|
||||
* @returns {Number} One of {@link CKEDITOR#EVENT_PHASE_CAPTURING},
|
||||
* {@link CKEDITOR#EVENT_PHASE_AT_TARGET}, or {@link CKEDITOR#EVENT_PHASE_BUBBLING}.
|
||||
*/
|
||||
getPhase: function() {
|
||||
return this.$.eventPhase || 2;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the coordinates of the mouse pointer relative to the top-left
|
||||
* corner of the document, in mouse related event.
|
||||
*
|
||||
* element.on( 'mousemouse', function( ev ) {
|
||||
* var pageOffset = ev.data.getPageOffset();
|
||||
* alert( pageOffset.x ); // page offset X
|
||||
* alert( pageOffset.y ); // page offset Y
|
||||
* } );
|
||||
*
|
||||
* @returns {Object} The object contains the position.
|
||||
* @returns {Number} return.x
|
||||
* @returns {Number} return.y
|
||||
*/
|
||||
getPageOffset : function() {
|
||||
var doc = this.getTarget().getDocument().$;
|
||||
var pageX = this.$.pageX || this.$.clientX + ( doc.documentElement.scrollLeft || doc.body.scrollLeft );
|
||||
var pageY = this.$.pageY || this.$.clientY + ( doc.documentElement.scrollTop || doc.body.scrollTop );
|
||||
return { x : pageX, y : pageY };
|
||||
}
|
||||
};
|
||||
|
||||
// For the followind constants, we need to go over the Unicode boundaries
|
||||
// (0x10FFFF) to avoid collision.
|
||||
|
||||
/**
|
||||
* CTRL key (0x110000).
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=0x110000]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.CTRL = 0x110000;
|
||||
|
||||
/**
|
||||
* SHIFT key (0x220000).
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=0x220000]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.SHIFT = 0x220000;
|
||||
|
||||
/**
|
||||
* ALT key (0x440000).
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=0x440000]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.ALT = 0x440000;
|
||||
|
||||
/**
|
||||
* Capturing phase.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=1]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.EVENT_PHASE_CAPTURING = 1;
|
||||
|
||||
/**
|
||||
* Event at target.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=2]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.EVENT_PHASE_AT_TARGET = 2;
|
||||
|
||||
/**
|
||||
* Bubbling phase.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=3]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.EVENT_PHASE_BUBBLING = 3;
|
||||
335
lib/ckeditor4/core/dom/iterator.js
Executable file
335
lib/ckeditor4/core/dom/iterator.js
Executable file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* File overview: DOM iterator, which iterates over list items, lines and paragraphs.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* Represents iterator class.
|
||||
*
|
||||
* @class CKEDITOR.dom.iterator
|
||||
* @constructor Creates an iterator class instance.
|
||||
* @param {CKEDITOR.dom.range} range
|
||||
* @todo
|
||||
*/
|
||||
function iterator( range ) {
|
||||
if ( arguments.length < 1 )
|
||||
return;
|
||||
|
||||
this.range = range;
|
||||
this.forceBrBreak = 0;
|
||||
|
||||
// Whether include <br>s into the enlarged range.(#3730).
|
||||
this.enlargeBr = 1;
|
||||
this.enforceRealBlocks = 0;
|
||||
|
||||
this._ || ( this._ = {} );
|
||||
}
|
||||
|
||||
var beginWhitespaceRegex = /^[\r\n\t ]+$/,
|
||||
// Ignore bookmark nodes.(#3783)
|
||||
bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ),
|
||||
whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ),
|
||||
skipGuard = function( node ) {
|
||||
return bookmarkGuard( node ) && whitespacesGuard( node );
|
||||
};
|
||||
|
||||
// Get a reference for the next element, bookmark nodes are skipped.
|
||||
function getNextSourceNode( node, startFromSibling, lastNode ) {
|
||||
var next = node.getNextSourceNode( startFromSibling, null, lastNode );
|
||||
while ( !bookmarkGuard( next ) )
|
||||
next = next.getNextSourceNode( startFromSibling, null, lastNode );
|
||||
return next;
|
||||
}
|
||||
|
||||
iterator.prototype = {
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
getNextParagraph: function( blockTag ) {
|
||||
blockTag = blockTag || 'p';
|
||||
|
||||
// Block-less range should be checked first.
|
||||
if ( !CKEDITOR.dtd[ this.range.root.getName() ][ blockTag ] )
|
||||
return null;
|
||||
|
||||
// The block element to be returned.
|
||||
var block;
|
||||
|
||||
// The range object used to identify the paragraph contents.
|
||||
var range;
|
||||
|
||||
// Indicats that the current element in the loop is the last one.
|
||||
var isLast;
|
||||
|
||||
// Indicate at least one of the range boundaries is inside a preformat block.
|
||||
var touchPre;
|
||||
|
||||
// Instructs to cleanup remaining BRs.
|
||||
var removePreviousBr, removeLastBr;
|
||||
|
||||
// This is the first iteration. Let's initialize it.
|
||||
if ( !this._.started ) {
|
||||
range = this.range.clone();
|
||||
|
||||
// Shrink the range to exclude harmful "noises" (#4087, #4450, #5435).
|
||||
range.shrink( CKEDITOR.NODE_ELEMENT, true );
|
||||
|
||||
touchPre = range.endContainer.hasAscendant( 'pre', true ) || range.startContainer.hasAscendant( 'pre', true );
|
||||
|
||||
range.enlarge( this.forceBrBreak && !touchPre || !this.enlargeBr ? CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS : CKEDITOR.ENLARGE_BLOCK_CONTENTS );
|
||||
|
||||
if ( !range.collapsed ) {
|
||||
var walker = new CKEDITOR.dom.walker( range.clone() ),
|
||||
ignoreBookmarkTextEvaluator = CKEDITOR.dom.walker.bookmark( true, true );
|
||||
// Avoid anchor inside bookmark inner text.
|
||||
walker.evaluator = ignoreBookmarkTextEvaluator;
|
||||
this._.nextNode = walker.next();
|
||||
// TODO: It's better to have walker.reset() used here.
|
||||
walker = new CKEDITOR.dom.walker( range.clone() );
|
||||
walker.evaluator = ignoreBookmarkTextEvaluator;
|
||||
var lastNode = walker.previous();
|
||||
this._.lastNode = lastNode.getNextSourceNode( true );
|
||||
|
||||
// We may have an empty text node at the end of block due to [3770].
|
||||
// If that node is the lastNode, it would cause our logic to leak to the
|
||||
// next block.(#3887)
|
||||
if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) {
|
||||
var testRange = this.range.clone();
|
||||
testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END );
|
||||
if ( testRange.checkEndOfBlock() ) {
|
||||
var path = new CKEDITOR.dom.elementPath( testRange.endContainer, testRange.root );
|
||||
var lastBlock = path.block || path.blockLimit;
|
||||
this._.lastNode = lastBlock.getNextSourceNode( true );
|
||||
}
|
||||
}
|
||||
|
||||
// Probably the document end is reached, we need a marker node.
|
||||
if ( !this._.lastNode ) {
|
||||
this._.lastNode = this._.docEndMarker = range.document.createText( '' );
|
||||
this._.lastNode.insertAfter( lastNode );
|
||||
}
|
||||
|
||||
// Let's reuse this variable.
|
||||
range = null;
|
||||
}
|
||||
|
||||
this._.started = 1;
|
||||
}
|
||||
|
||||
var currentNode = this._.nextNode;
|
||||
lastNode = this._.lastNode;
|
||||
|
||||
this._.nextNode = null;
|
||||
while ( currentNode ) {
|
||||
// closeRange indicates that a paragraph boundary has been found,
|
||||
// so the range can be closed.
|
||||
var closeRange = 0,
|
||||
parentPre = currentNode.hasAscendant( 'pre' );
|
||||
|
||||
// includeNode indicates that the current node is good to be part
|
||||
// of the range. By default, any non-element node is ok for it.
|
||||
var includeNode = ( currentNode.type != CKEDITOR.NODE_ELEMENT ),
|
||||
continueFromSibling = 0;
|
||||
|
||||
// If it is an element node, let's check if it can be part of the
|
||||
// range.
|
||||
if ( !includeNode ) {
|
||||
var nodeName = currentNode.getName();
|
||||
|
||||
if ( currentNode.isBlockBoundary( this.forceBrBreak && !parentPre && { br:1 } ) ) {
|
||||
// <br> boundaries must be part of the range. It will
|
||||
// happen only if ForceBrBreak.
|
||||
if ( nodeName == 'br' )
|
||||
includeNode = 1;
|
||||
else if ( !range && !currentNode.getChildCount() && nodeName != 'hr' ) {
|
||||
// If we have found an empty block, and haven't started
|
||||
// the range yet, it means we must return this block.
|
||||
block = currentNode;
|
||||
isLast = currentNode.equals( lastNode );
|
||||
break;
|
||||
}
|
||||
|
||||
// The range must finish right before the boundary,
|
||||
// including possibly skipped empty spaces. (#1603)
|
||||
if ( range ) {
|
||||
range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
|
||||
|
||||
// The found boundary must be set as the next one at this
|
||||
// point. (#1717)
|
||||
if ( nodeName != 'br' )
|
||||
this._.nextNode = currentNode;
|
||||
}
|
||||
|
||||
closeRange = 1;
|
||||
} else {
|
||||
// If we have child nodes, let's check them.
|
||||
if ( currentNode.getFirst() ) {
|
||||
// If we don't have a range yet, let's start it.
|
||||
if ( !range ) {
|
||||
range = this.range.clone();
|
||||
range.setStartAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
|
||||
}
|
||||
|
||||
currentNode = currentNode.getFirst();
|
||||
continue;
|
||||
}
|
||||
includeNode = 1;
|
||||
}
|
||||
} else if ( currentNode.type == CKEDITOR.NODE_TEXT ) {
|
||||
// Ignore normal whitespaces (i.e. not including or
|
||||
// other unicode whitespaces) before/after a block node.
|
||||
if ( beginWhitespaceRegex.test( currentNode.getText() ) )
|
||||
includeNode = 0;
|
||||
}
|
||||
|
||||
// The current node is good to be part of the range and we are
|
||||
// starting a new range, initialize it first.
|
||||
if ( includeNode && !range ) {
|
||||
range = this.range.clone();
|
||||
range.setStartAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
|
||||
}
|
||||
|
||||
// The last node has been found.
|
||||
isLast = ( ( !closeRange || includeNode ) && currentNode.equals( lastNode ) );
|
||||
|
||||
// If we are in an element boundary, let's check if it is time
|
||||
// to close the range, otherwise we include the parent within it.
|
||||
if ( range && !closeRange ) {
|
||||
while ( !currentNode.getNext( skipGuard ) && !isLast ) {
|
||||
var parentNode = currentNode.getParent();
|
||||
|
||||
if ( parentNode.isBlockBoundary( this.forceBrBreak && !parentPre && { br:1 } ) ) {
|
||||
closeRange = 1;
|
||||
includeNode = 0;
|
||||
isLast = isLast || ( parentNode.equals( lastNode ) );
|
||||
// Make sure range includes bookmarks at the end of the block. (#7359)
|
||||
range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END );
|
||||
break;
|
||||
}
|
||||
|
||||
currentNode = parentNode;
|
||||
includeNode = 1;
|
||||
isLast = ( currentNode.equals( lastNode ) );
|
||||
continueFromSibling = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Now finally include the node.
|
||||
if ( includeNode )
|
||||
range.setEndAt( currentNode, CKEDITOR.POSITION_AFTER_END );
|
||||
|
||||
currentNode = getNextSourceNode( currentNode, continueFromSibling, lastNode );
|
||||
isLast = !currentNode;
|
||||
|
||||
// We have found a block boundary. Let's close the range and move out of the
|
||||
// loop.
|
||||
if ( isLast || ( closeRange && range ) )
|
||||
break;
|
||||
}
|
||||
|
||||
// Now, based on the processed range, look for (or create) the block to be returned.
|
||||
if ( !block ) {
|
||||
// If no range has been found, this is the end.
|
||||
if ( !range ) {
|
||||
this._.docEndMarker && this._.docEndMarker.remove();
|
||||
this._.nextNode = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
var startPath = new CKEDITOR.dom.elementPath( range.startContainer, range.root );
|
||||
var startBlockLimit = startPath.blockLimit,
|
||||
checkLimits = { div:1,th:1,td:1 };
|
||||
block = startPath.block;
|
||||
|
||||
if ( !block && startBlockLimit && !this.enforceRealBlocks && checkLimits[ startBlockLimit.getName() ] && range.checkStartOfBlock() && range.checkEndOfBlock() && !startBlockLimit.equals( range.root ) )
|
||||
block = startBlockLimit;
|
||||
else if ( !block || ( this.enforceRealBlocks && block.getName() == 'li' ) ) {
|
||||
// Create the fixed block.
|
||||
block = this.range.document.createElement( blockTag );
|
||||
|
||||
// Move the contents of the temporary range to the fixed block.
|
||||
range.extractContents().appendTo( block );
|
||||
block.trim();
|
||||
|
||||
// Insert the fixed block into the DOM.
|
||||
range.insertNode( block );
|
||||
|
||||
removePreviousBr = removeLastBr = true;
|
||||
} else if ( block.getName() != 'li' ) {
|
||||
// If the range doesn't includes the entire contents of the
|
||||
// block, we must split it, isolating the range in a dedicated
|
||||
// block.
|
||||
if ( !range.checkStartOfBlock() || !range.checkEndOfBlock() ) {
|
||||
// The resulting block will be a clone of the current one.
|
||||
block = block.clone( false );
|
||||
|
||||
// Extract the range contents, moving it to the new block.
|
||||
range.extractContents().appendTo( block );
|
||||
block.trim();
|
||||
|
||||
// Split the block. At this point, the range will be in the
|
||||
// right position for our intents.
|
||||
var splitInfo = range.splitBlock();
|
||||
|
||||
removePreviousBr = !splitInfo.wasStartOfBlock;
|
||||
removeLastBr = !splitInfo.wasEndOfBlock;
|
||||
|
||||
// Insert the new block into the DOM.
|
||||
range.insertNode( block );
|
||||
}
|
||||
} else if ( !isLast ) {
|
||||
// LIs are returned as is, with all their children (due to the
|
||||
// nested lists). But, the next node is the node right after
|
||||
// the current range, which could be an <li> child (nested
|
||||
// lists) or the next sibling <li>.
|
||||
|
||||
this._.nextNode = ( block.equals( lastNode ) ? null : getNextSourceNode( range.getBoundaryNodes().endNode, 1, lastNode ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( removePreviousBr ) {
|
||||
var previousSibling = block.getPrevious();
|
||||
if ( previousSibling && previousSibling.type == CKEDITOR.NODE_ELEMENT ) {
|
||||
if ( previousSibling.getName() == 'br' )
|
||||
previousSibling.remove();
|
||||
else if ( previousSibling.getLast() && previousSibling.getLast().$.nodeName.toLowerCase() == 'br' )
|
||||
previousSibling.getLast().remove();
|
||||
}
|
||||
}
|
||||
|
||||
if ( removeLastBr ) {
|
||||
var lastChild = block.getLast();
|
||||
if ( lastChild && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.getName() == 'br' ) {
|
||||
// Take care not to remove the block expanding <br> in non-IE browsers.
|
||||
if ( CKEDITOR.env.ie || lastChild.getPrevious( bookmarkGuard ) || lastChild.getNext( bookmarkGuard ) )
|
||||
lastChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Get a reference for the next element. This is important because the
|
||||
// above block can be removed or changed, so we can rely on it for the
|
||||
// next interation.
|
||||
if ( !this._.nextNode ) {
|
||||
this._.nextNode = ( isLast || block.equals( lastNode ) || !lastNode ) ? null : getNextSourceNode( block, 1, lastNode );
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates {CKEDITOR.dom.iterator} instance for this range.
|
||||
*
|
||||
* @member CKEDITOR.dom.range
|
||||
* @returns {CKEDITOR.dom.iterator}
|
||||
*/
|
||||
CKEDITOR.dom.range.prototype.createIterator = function() {
|
||||
return new iterator( this );
|
||||
};
|
||||
})();
|
||||
741
lib/ckeditor4/core/dom/node.js
Executable file
741
lib/ckeditor4/core/dom/node.js
Executable file
@@ -0,0 +1,741 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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}.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.dom.domObject
|
||||
* @constructor Creates a node class instance.
|
||||
* @param {Object} domNode A native DOM node.
|
||||
* @see CKEDITOR.dom.element
|
||||
* @see CKEDITOR.dom.text
|
||||
*/
|
||||
CKEDITOR.dom.node = function( domNode ) {
|
||||
if ( domNode ) {
|
||||
var type = domNode.nodeType == CKEDITOR.NODE_DOCUMENT ? 'document' : domNode.nodeType == CKEDITOR.NODE_ELEMENT ? 'element' : domNode.nodeType == CKEDITOR.NODE_TEXT ? 'text' : domNode.nodeType == CKEDITOR.NODE_COMMENT ? 'comment' : domNode.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ? 'documentFragment' : 'domObject'; // Call the base constructor otherwise.
|
||||
|
||||
return new CKEDITOR.dom[ type ]( domNode );
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject();
|
||||
|
||||
/**
|
||||
* Element node type.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=1]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.NODE_ELEMENT = 1;
|
||||
|
||||
/**
|
||||
* Document node type.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=9]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.NODE_DOCUMENT = 9;
|
||||
|
||||
/**
|
||||
* Text node type.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=3]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.NODE_TEXT = 3;
|
||||
|
||||
/**
|
||||
* Comment node type.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=8]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
CKEDITOR.NODE_COMMENT = 8;
|
||||
|
||||
/**
|
||||
* Document fragment node type.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=11]
|
||||
* @member CKEDITOR
|
||||
*/
|
||||
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, {
|
||||
/**
|
||||
* Makes this node a child of another element.
|
||||
*
|
||||
* var p = new CKEDITOR.dom.element( 'p' );
|
||||
* var strong = new CKEDITOR.dom.element( 'strong' );
|
||||
* strong.appendTo( p );
|
||||
*
|
||||
* // Result: '<p><strong></strong></p>'.
|
||||
*
|
||||
* @param {CKEDITOR.dom.element} element The target element to which this node will be appended.
|
||||
* @returns {CKEDITOR.dom.element} The target element.
|
||||
*/
|
||||
appendTo: function( element, toStart ) {
|
||||
element.append( this, toStart );
|
||||
return element;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone this node.
|
||||
*
|
||||
* **Note**: Values set by {#setCustomData} won't be available in the clone.
|
||||
*
|
||||
* @param {Boolean} [includeChildren=false] If `true` then all node's
|
||||
* children will be cloned recursively.
|
||||
* @param {Boolean} [cloneId=false] Whether ID attributes should be cloned too.
|
||||
* @returns {CKEDITOR.dom.node} Clone of this node.
|
||||
*/
|
||||
clone: function( includeChildren, cloneId ) {
|
||||
var $clone = this.$.cloneNode( includeChildren );
|
||||
|
||||
var removeIds = function( node ) {
|
||||
// Reset data-cke-expando only when has been cloned (IE and only for some types of objects).
|
||||
if ( node['data-cke-expando'] )
|
||||
node['data-cke-expando'] = false;
|
||||
|
||||
if ( node.nodeType != CKEDITOR.NODE_ELEMENT )
|
||||
return;
|
||||
if ( !cloneId )
|
||||
node.removeAttribute( 'id', 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 );
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if node is preceded by any sibling.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasPrevious: function() {
|
||||
return !!this.$.previousSibling;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if node is succeeded by any sibling.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasNext: function() {
|
||||
return !!this.$.nextSibling;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts this element after a node.
|
||||
*
|
||||
* var em = new CKEDITOR.dom.element( 'em' );
|
||||
* var strong = new CKEDITOR.dom.element( 'strong' );
|
||||
* strong.insertAfter( em );
|
||||
*
|
||||
* // Result: '<em></em><strong></strong>'
|
||||
*
|
||||
* @param {CKEDITOR.dom.node} node The node that will precede this element.
|
||||
* @returns {CKEDITOR.dom.node} The node preceding this one after insertion.
|
||||
*/
|
||||
insertAfter: function( node ) {
|
||||
node.$.parentNode.insertBefore( this.$, node.$.nextSibling );
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts this element before a node.
|
||||
*
|
||||
* var em = new CKEDITOR.dom.element( 'em' );
|
||||
* var strong = new CKEDITOR.dom.element( 'strong' );
|
||||
* strong.insertBefore( em );
|
||||
*
|
||||
* // result: '<strong></strong><em></em>'
|
||||
*
|
||||
* @param {CKEDITOR.dom.node} node The node that will succeed this element.
|
||||
* @returns {CKEDITOR.dom.node} The node being inserted.
|
||||
*/
|
||||
insertBefore: function( node ) {
|
||||
node.$.parentNode.insertBefore( this.$, node.$ );
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts node before this node.
|
||||
*
|
||||
* var em = new CKEDITOR.dom.element( 'em' );
|
||||
* var strong = new CKEDITOR.dom.element( 'strong' );
|
||||
* strong.insertBeforeMe( em );
|
||||
*
|
||||
* // result: '<em></em><strong></strong>'
|
||||
*
|
||||
* @param {CKEDITOR.dom.node} node The node that will preceed this element.
|
||||
* @returns {CKEDITOR.dom.node} The node being inserted.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param {Boolean} [normalized=false] See {@link #getIndex}.
|
||||
* @returns {Array} The address.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* var element = CKEDITOR.document.getById( 'example' );
|
||||
* alert( element.getDocument().equals( CKEDITOR.document ) ); // true
|
||||
*
|
||||
* @returns {CKEDITOR.dom.document} The document.
|
||||
*/
|
||||
getDocument: function() {
|
||||
return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get index of a node in an array of its parent.childNodes.
|
||||
*
|
||||
* Let's assume having childNodes array:
|
||||
*
|
||||
* [ emptyText, element1, text, text, element2 ]
|
||||
* element1.getIndex(); // 1
|
||||
* element1.getIndex( true ); // 0
|
||||
* element2.getIndex(); // 4
|
||||
* element2.getIndex( true ); // 2
|
||||
*
|
||||
* @param {Boolean} normalized When `true` empty text nodes and one followed
|
||||
* by another one text node are not counted in.
|
||||
* @returns {Number} Index of a node.
|
||||
*/
|
||||
getIndex: function( normalized ) {
|
||||
// Attention: getAddress depends on this.$
|
||||
// getIndex is called on a plain object: { $ : node }
|
||||
|
||||
var current = this.$,
|
||||
index = -1,
|
||||
isNormalizing;
|
||||
|
||||
if ( !this.$.parentNode )
|
||||
return index;
|
||||
|
||||
do {
|
||||
// Bypass blank node and adjacent text nodes.
|
||||
if ( normalized && current != this.$ && current.nodeType == CKEDITOR.NODE_TEXT && ( isNormalizing || !current.nodeValue ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
isNormalizing = current.nodeType == CKEDITOR.NODE_TEXT;
|
||||
}
|
||||
while ( ( current = current.previousSibling ) )
|
||||
|
||||
return index;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the node that preceed this element in its parent's child list.
|
||||
*
|
||||
* var element = CKEDITOR.dom.element.createFromHtml( '<div><i>prev</i><b>Example</b></div>' );
|
||||
* var first = element.getLast().getPrev();
|
||||
* alert( first.getName() ); // 'i'
|
||||
*
|
||||
* @param {Function} [evaluator] Filtering the result node.
|
||||
* @returns {CKEDITOR.dom.node} The previous node or null if not available.
|
||||
*/
|
||||
getPrevious: function( evaluator ) {
|
||||
var previous = this.$,
|
||||
retval;
|
||||
do {
|
||||
previous = previous.previousSibling;
|
||||
|
||||
// Avoid returning the doc type node.
|
||||
// http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-412266927
|
||||
retval = previous && previous.nodeType != 10 && 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.
|
||||
*
|
||||
* var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b><i>next</i></div>' );
|
||||
* var last = element.getFirst().getNext();
|
||||
* alert( last.getName() ); // 'i'
|
||||
*
|
||||
* @param {Function} [evaluator] Filtering the result node.
|
||||
* @returns {CKEDITOR.dom.node} The next node or null if not available.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* var node = editor.document.getBody().getFirst();
|
||||
* var parent = node.getParent();
|
||||
* alert( node.getName() ); // 'body'
|
||||
*
|
||||
* @param {Boolean} [allowFragmentParent=false] Consider also parent node that is of
|
||||
* fragment type {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}.
|
||||
* @returns {CKEDITOR.dom.element} The parent element.
|
||||
*/
|
||||
getParent: function( allowFragmentParent ) {
|
||||
var parent = this.$.parentNode;
|
||||
return ( parent && ( parent.nodeType == CKEDITOR.NODE_ELEMENT || allowFragmentParent && parent.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) ) ? new CKEDITOR.dom.node( parent ) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
getParents: function( closerFirst ) {
|
||||
var node = this;
|
||||
var parents = [];
|
||||
|
||||
do {
|
||||
parents[ closerFirst ? 'push' : 'unshift' ]( node );
|
||||
}
|
||||
while ( ( node = node.getParent() ) )
|
||||
|
||||
return parents;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* // 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">
|
||||
*
|
||||
* @since 3.6.1
|
||||
* @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.
|
||||
*/
|
||||
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( $ );
|
||||
|
||||
try {
|
||||
$ = $.parentNode;
|
||||
} catch( e ) {
|
||||
$ = null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
hasAscendant: function( name, includeSelf ) {
|
||||
var $ = this.$;
|
||||
|
||||
if ( !includeSelf )
|
||||
$ = $.parentNode;
|
||||
|
||||
while ( $ ) {
|
||||
if ( $.nodeName && $.nodeName.toLowerCase() == name )
|
||||
return true;
|
||||
|
||||
$ = $.parentNode;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
move: function( target, toStart ) {
|
||||
target.append( this.remove(), toStart );
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes this node from the document DOM.
|
||||
*
|
||||
* var element = CKEDITOR.document.getById( 'MyElement' );
|
||||
* element.remove();
|
||||
*
|
||||
* @param {Boolean} [preserveChildren=false] Indicates that the children
|
||||
* elements must remain in the document, removing only the outer tags.
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
replace: function( nodeToReplace ) {
|
||||
this.insertBefore( nodeToReplace );
|
||||
nodeToReplace.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
trim: function() {
|
||||
this.ltrim();
|
||||
this.rtrim();
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
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).
|
||||
*
|
||||
* **Note:** When `attributeCheck` is not used, this method only work for elements
|
||||
* that are already presented in the document, otherwise the result
|
||||
* is not guaranteed, it's mainly for performance consideration.
|
||||
*
|
||||
* // For the following HTML:
|
||||
* // <div contenteditable="false">Some <b>text</b></div>
|
||||
*
|
||||
* // If "ele" is the above <div>
|
||||
* element.isReadOnly(); // true
|
||||
*
|
||||
* @since 3.5
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isReadOnly: function() {
|
||||
var element = this;
|
||||
if ( this.type != CKEDITOR.NODE_ELEMENT )
|
||||
element = this.getParent();
|
||||
|
||||
if ( element && typeof element.$.isContentEditable != 'undefined' )
|
||||
return !( element.$.isContentEditable || element.data( 'cke-editable' ) );
|
||||
else {
|
||||
// Degrade for old browsers which don't support "isContentEditable", e.g. FF3
|
||||
|
||||
while ( element ) {
|
||||
if ( element.data( 'cke-editable' ) )
|
||||
break;
|
||||
|
||||
if ( element.getAttribute( 'contentEditable' ) == 'false' )
|
||||
return true;
|
||||
else if ( element.getAttribute( 'contentEditable' ) == 'true' )
|
||||
break;
|
||||
|
||||
element = element.getParent();
|
||||
}
|
||||
|
||||
// Reached the root of DOM tree, no editable found.
|
||||
return !element;
|
||||
}
|
||||
}
|
||||
});
|
||||
43
lib/ckeditor4/core/dom/nodelist.js
Executable file
43
lib/ckeditor4/core/dom/nodelist.js
Executable file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a list of {@link CKEDITOR.dom.node} objects.
|
||||
* It's a wrapper for native nodes list.
|
||||
*
|
||||
* var nodeList = CKEDITOR.document.getBody().getChildren();
|
||||
* alert( nodeList.count() ); // number [0;N]
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a document class instance.
|
||||
* @param {Object} nativeList
|
||||
*/
|
||||
CKEDITOR.dom.nodeList = function( nativeList ) {
|
||||
this.$ = nativeList;
|
||||
};
|
||||
|
||||
CKEDITOR.dom.nodeList.prototype = {
|
||||
/**
|
||||
* Get count of nodes in this list.
|
||||
*
|
||||
* @returns {Number}
|
||||
*/
|
||||
count: function() {
|
||||
return this.$.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get node from the list.
|
||||
*
|
||||
* @returns {CKEDITOR.dom.node}
|
||||
*/
|
||||
getItem: function( index ) {
|
||||
if ( index < 0 || index >= this.$.length )
|
||||
return null;
|
||||
|
||||
var $node = this.$[ index ];
|
||||
return $node ? new CKEDITOR.dom.node( $node ) : null;
|
||||
}
|
||||
};
|
||||
2200
lib/ckeditor4/core/dom/range.js
Executable file
2200
lib/ckeditor4/core/dom/range.js
Executable file
File diff suppressed because it is too large
Load Diff
201
lib/ckeditor4/core/dom/rangelist.js
Executable file
201
lib/ckeditor4/core/dom/rangelist.js
Executable file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* Represents a list os CKEDITOR.dom.range objects, which can be easily
|
||||
* iterated sequentially.
|
||||
*
|
||||
* @class
|
||||
* @extends Array
|
||||
* @constructor Creates a rangeList class instance.
|
||||
* @param {CKEDITOR.dom.range/CKEDITOR.dom.range[]} [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 = {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Retrieves the next range in the list.
|
||||
*
|
||||
* @member CKEDITOR.dom.rangeListIterator
|
||||
* @param {Boolean} [mergeConsequent=false] 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;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create bookmarks for all ranges. See {@link CKEDITOR.dom.range#createBookmark}.
|
||||
*
|
||||
* @param {Boolean} [serializable=false] See {@link CKEDITOR.dom.range#createBookmark}.
|
||||
* @returns {Array} Array of bookmarks.
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create "unobtrusive" bookmarks for all ranges. See {@link CKEDITOR.dom.range#createBookmark2}.
|
||||
*
|
||||
* @param {Boolean} [normalized=false] See {@link CKEDITOR.dom.range#createBookmark2}.
|
||||
* @returns {Array} Array of bookmarks.
|
||||
*/
|
||||
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}.
|
||||
*
|
||||
* @class CKEDITOR.dom.rangeListIterator
|
||||
*/
|
||||
139
lib/ckeditor4/core/dom/text.js
Executable file
139
lib/ckeditor4/core/dom/text.js
Executable file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.dom.text} class, which represents
|
||||
* a DOM text node.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a DOM text node.
|
||||
*
|
||||
* var nativeNode = document.createTextNode( 'Example' );
|
||||
* var text = CKEDITOR.dom.text( nativeNode );
|
||||
*
|
||||
* var text = CKEDITOR.dom.text( 'Example' );
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.dom.node
|
||||
* @constructor Creates a text class instance.
|
||||
* @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.
|
||||
*/
|
||||
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 );
|
||||
|
||||
this.$ = text;
|
||||
};
|
||||
|
||||
CKEDITOR.dom.text.prototype = new CKEDITOR.dom.node();
|
||||
|
||||
CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype, {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_TEXT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_TEXT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_TEXT,
|
||||
|
||||
/**
|
||||
* Gets length of node's value.
|
||||
*
|
||||
* @returns {Number}
|
||||
*/
|
||||
getLength: function() {
|
||||
return this.$.nodeValue.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets node's value.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
getText: function() {
|
||||
return this.$.nodeValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets node's value.
|
||||
*
|
||||
* @param {String} text
|
||||
*/
|
||||
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 ) {
|
||||
|
||||
// Saved the children count and text length beforehand.
|
||||
var parent = this.$.parentNode,
|
||||
count = parent.childNodes.length,
|
||||
length = this.getLength();
|
||||
|
||||
var doc = this.getDocument();
|
||||
var retval = new CKEDITOR.dom.text( this.$.splitText( offset ), doc );
|
||||
|
||||
if ( parent.childNodes.length == count )
|
||||
{
|
||||
// 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 ( offset >= length )
|
||||
{
|
||||
retval = doc.createText( '' );
|
||||
retval.insertAfter( this );
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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)
|
||||
var workaround = doc.createText( '' );
|
||||
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 );
|
||||
}
|
||||
});
|
||||
477
lib/ckeditor4/core/dom/walker.js
Executable file
477
lib/ckeditor4/core/dom/walker.js
Executable file
@@ -0,0 +1,477 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
// This function is to be called under a "walker" instance scope.
|
||||
function iterate( rtl, breakOnFalse ) {
|
||||
var range = this.range;
|
||||
|
||||
// Return null if we have reached the end.
|
||||
if ( this._.end )
|
||||
return null;
|
||||
|
||||
// This is the first call. Initialize it.
|
||||
if ( !this._.start ) {
|
||||
this._.start = 1;
|
||||
|
||||
// A collapsed range must return null at first call.
|
||||
if ( range.collapsed ) {
|
||||
this.end();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Move outside of text node edges.
|
||||
range.optimize();
|
||||
}
|
||||
|
||||
var node,
|
||||
startCt = range.startContainer,
|
||||
endCt = range.endContainer,
|
||||
startOffset = range.startOffset,
|
||||
endOffset = range.endOffset,
|
||||
guard,
|
||||
userGuard = this.guard,
|
||||
type = this.type,
|
||||
getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );
|
||||
|
||||
// Create the LTR guard function, if necessary.
|
||||
if ( !rtl && !this._.guardLTR ) {
|
||||
// The node that stops walker from moving up.
|
||||
var limitLTR = endCt.type == CKEDITOR.NODE_ELEMENT ? endCt : endCt.getParent();
|
||||
|
||||
// The node that stops the walker from going to next.
|
||||
var blockerLTR = endCt.type == CKEDITOR.NODE_ELEMENT ? endCt.getChild( endOffset ) : endCt.getNext();
|
||||
|
||||
this._.guardLTR = function( node, movingOut ) {
|
||||
return ( ( !movingOut || !limitLTR.equals( node ) ) && ( !blockerLTR || !node.equals( blockerLTR ) ) && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) );
|
||||
};
|
||||
}
|
||||
|
||||
// Create the RTL guard function, if necessary.
|
||||
if ( rtl && !this._.guardRTL ) {
|
||||
// The node that stops walker from moving up.
|
||||
var limitRTL = startCt.type == CKEDITOR.NODE_ELEMENT ? startCt : startCt.getParent();
|
||||
|
||||
// The node that stops the walker from going to next.
|
||||
var blockerRTL = startCt.type == CKEDITOR.NODE_ELEMENT ? startOffset ? startCt.getChild( startOffset - 1 ) : null : startCt.getPrevious();
|
||||
|
||||
this._.guardRTL = function( node, movingOut ) {
|
||||
return ( ( !movingOut || !limitRTL.equals( node ) ) && ( !blockerRTL || !node.equals( blockerRTL ) ) && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) );
|
||||
};
|
||||
}
|
||||
|
||||
// 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 = endCt;
|
||||
|
||||
if ( node.type == CKEDITOR.NODE_ELEMENT ) {
|
||||
if ( endOffset > 0 )
|
||||
node = node.getChild( endOffset - 1 );
|
||||
else
|
||||
node = ( guard( node, true ) === false ) ? null : node.getPreviousSourceNode( true, type, guard );
|
||||
}
|
||||
} else {
|
||||
node = startCt;
|
||||
|
||||
if ( node.type == CKEDITOR.NODE_ELEMENT ) {
|
||||
if ( !( node = node.getChild( startOffset ) ) )
|
||||
node = ( guard( startCt, true ) === false ) ? null : startCt.getNextSourceNode( true, type, guard );
|
||||
}
|
||||
}
|
||||
|
||||
if ( node && guard( node ) === false )
|
||||
node = null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
CKEDITOR.dom.walker = CKEDITOR.tools.createClass({
|
||||
/**
|
||||
* Creates a walker class instance.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @property {Function} evaluator
|
||||
*/
|
||||
// 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.
|
||||
|
||||
* @property {Function} guard
|
||||
*/
|
||||
// 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: {
|
||||
/**
|
||||
* Stops 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 );
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets walker.
|
||||
*/
|
||||
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 };
|
||||
|
||||
/**
|
||||
* @member CKEDITOR.dom.element
|
||||
* @todo
|
||||
*/
|
||||
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() ];
|
||||
};
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @todo
|
||||
*/
|
||||
CKEDITOR.dom.walker.blockBoundary = function( customNodeNames ) {
|
||||
return function( node, type ) {
|
||||
return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary( customNodeNames ) );
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @todo
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @static
|
||||
* @param {Boolean} [contentOnly=false] Whether only test against the text content of
|
||||
* bookmark node instead of the element itself (default).
|
||||
* @param {Boolean} [isReject=false] Whether should return `false` for the bookmark
|
||||
* node instead of `true` (default).
|
||||
* @returns {Function}
|
||||
*/
|
||||
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.type != CKEDITOR.NODE_ELEMENT && ( 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.
|
||||
*
|
||||
* @static
|
||||
* @param {Boolean} [isReject=false]
|
||||
* @returns {Function}
|
||||
*/
|
||||
CKEDITOR.dom.walker.whitespaces = function( isReject ) {
|
||||
return function( node ) {
|
||||
var isWhitespace;
|
||||
if ( node && node.type == CKEDITOR.NODE_TEXT ) {
|
||||
// whitespace, as well as the text cursor filler node we used in Webkit. (#9384)
|
||||
isWhitespace = !CKEDITOR.tools.trim( node.getText() ) ||
|
||||
CKEDITOR.env.webkit && node.getText() == '\u200b';
|
||||
}
|
||||
|
||||
return !! ( isReject ^ isWhitespace );
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the node is invisible in wysiwyg mode.
|
||||
*
|
||||
* @static
|
||||
* @param {Boolean} [isReject=false]
|
||||
* @returns {Function}
|
||||
*/
|
||||
CKEDITOR.dom.walker.invisible = function( isReject ) {
|
||||
var whitespace = CKEDITOR.dom.walker.whitespaces();
|
||||
return function( node ) {
|
||||
var invisible;
|
||||
|
||||
if ( whitespace( node ) )
|
||||
invisible = 1;
|
||||
else {
|
||||
// Visibility should be checked on element.
|
||||
if ( node.type == CKEDITOR.NODE_TEXT )
|
||||
node = node.getParent();
|
||||
|
||||
// Nodes that take no spaces in wysiwyg:
|
||||
// 1. White-spaces but not including NBSP;
|
||||
// 2. Empty inline elements, e.g. <b></b> we're checking here
|
||||
// 'offsetHeight' instead of 'offsetWidth' for properly excluding
|
||||
// all sorts of empty paragraph, e.g. <br />.
|
||||
invisible = !node.$.offsetHeight;
|
||||
}
|
||||
|
||||
return !!( isReject ^ invisible );
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param {Number} type
|
||||
* @param {Boolean} [isReject=false]
|
||||
* @returns {Function}
|
||||
* @todo
|
||||
*/
|
||||
CKEDITOR.dom.walker.nodeType = function( type, isReject ) {
|
||||
return function( node ) {
|
||||
return !!( isReject ^ ( node.type == type ) );
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param {Boolean} [isReject=false]
|
||||
* @returns {Function}
|
||||
* @todo
|
||||
*/
|
||||
CKEDITOR.dom.walker.bogus = function( isReject ) {
|
||||
function nonEmpty( node ) {
|
||||
return !isWhitespaces( node ) && !isBookmark( node );
|
||||
}
|
||||
|
||||
return function( node ) {
|
||||
var isBogus = !CKEDITOR.env.ie ? node.is && node.is( 'br' ) : node.getText && tailNbspRegex.test( node.getText() );
|
||||
|
||||
if ( isBogus ) {
|
||||
var parent = node.getParent(),
|
||||
next = node.getNext( nonEmpty );
|
||||
|
||||
isBogus = parent.isBlockBoundary() && ( !next || next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() );
|
||||
}
|
||||
|
||||
return !!( isReject ^ isBogus );
|
||||
};
|
||||
};
|
||||
|
||||
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.
|
||||
*
|
||||
* @member CKEDITOR.dom.element
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
CKEDITOR.dom.element.prototype.getBogus = function() {
|
||||
// Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#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;
|
||||
};
|
||||
|
||||
})();
|
||||
95
lib/ckeditor4/core/dom/window.js
Executable file
95
lib/ckeditor4/core/dom/window.js
Executable file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.dom.document} class, which
|
||||
* represents a DOM document.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a DOM window.
|
||||
*
|
||||
* var document = new CKEDITOR.dom.window( window );
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.dom.domObject
|
||||
* @constructor Creates a window class instance.
|
||||
* @param {Object} domWindow A native DOM 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, {
|
||||
/**
|
||||
* Moves the selection focus to this window.
|
||||
*
|
||||
* var win = new CKEDITOR.dom.window( window );
|
||||
* win.focus();
|
||||
*/
|
||||
focus: function() {
|
||||
this.$.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the width and height of this window's viewable area.
|
||||
*
|
||||
* var win = new CKEDITOR.dom.window( window );
|
||||
* var size = win.getViewPaneSize();
|
||||
* alert( size.width );
|
||||
* alert( size.height );
|
||||
*
|
||||
* @returns {Object} An object with the `width` and `height`
|
||||
* properties containing the size.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* var win = new CKEDITOR.dom.window( window );
|
||||
* var pos = win.getScrollPosition();
|
||||
* alert( pos.x );
|
||||
* alert( pos.y );
|
||||
*
|
||||
* @returns {Object} An object with the `x` and `y` properties
|
||||
* containing the scroll position.
|
||||
*/
|
||||
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
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the frame element containing this window context.
|
||||
*
|
||||
* @returns {CKEDITOR.dom.element} The frame element or `null` if not in a frame context.
|
||||
*/
|
||||
getFrame: function() {
|
||||
var iframe = this.$.frameElement;
|
||||
return iframe ? new CKEDITOR.dom.element.get( iframe ) : null;
|
||||
}
|
||||
});
|
||||
302
lib/ckeditor4/core/dtd.js
Executable file
302
lib/ckeditor4/core/dtd.js
Executable file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* // Check if <div> can be contained in a <p> element.
|
||||
* alert( !!CKEDITOR.dtd[ 'p' ][ 'div' ] ); // false
|
||||
*
|
||||
* // Check if <p> can be contained in a <div> element.
|
||||
* alert( !!CKEDITOR.dtd[ 'div' ][ 'p' ] ); // true
|
||||
*
|
||||
* // Check if <p> is a block element.
|
||||
* alert( !!CKEDITOR.dtd.$block[ 'p' ] ); // true
|
||||
*
|
||||
* @class CKEDITOR.dtd
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.dtd = (function() {
|
||||
'use strict';
|
||||
|
||||
var X = CKEDITOR.tools.extend,
|
||||
// Subtraction rest of sets, from the first set.
|
||||
Y = function( source, removed ) {
|
||||
var substracted = CKEDITOR.tools.clone( source );
|
||||
for ( var i = 1; i < arguments.length; i++ ) {
|
||||
removed = arguments[ i ];
|
||||
for( var name in removed )
|
||||
delete substracted[ name ];
|
||||
}
|
||||
return substracted;
|
||||
};
|
||||
|
||||
// Phrasing elements.
|
||||
// P = { a:1,em:1,strong:1,small:1,abbr:1,dfn:1,i:1,b:1,s:1,u:1,code:1,'var':1,samp:1,kbd:1,sup:1,sub:1,q:1,cite:1,span:1,bdo:1,bdi:1,br:1,wbr:1,ins:1,del:1,img:1,embed:1,object:1,iframe:1,map:1,area:1,script:1,noscript:1,ruby:1,video:1,audio:1,input:1,textarea:1,select:1,button:1,label:1,output:1,keygen:1,progress:1,command:1,canvas:1,time:1,meter:1,detalist:1 },
|
||||
|
||||
// Flow elements.
|
||||
// F = { a:1,p:1,hr:1,pre:1,ul:1,ol:1,dl:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hgroup:1,address:1,blockquote:1,ins:1,del:1,object:1,map:1,noscript:1,section:1,nav:1,article:1,aside:1,header:1,footer:1,video:1,audio:1,figure:1,table:1,form:1,fieldset:1,menu:1,canvas:1,details:1 },
|
||||
|
||||
// Text can be everywhere.
|
||||
// X( P, T );
|
||||
// Flow elements set consists of phrasing elements set.
|
||||
// X( F, P );
|
||||
|
||||
var P = {}, F = {},
|
||||
// Intersection of flow elements set and phrasing elements set.
|
||||
PF = { a:1,abbr:1,area:1,audio:1,b:1,bdi:1,bdo:1,br:1,button:1,canvas:1,cite:1,code:1,command:1,datalist:1,del:1,dfn:1,em:1,embed:1,i:1,iframe:1,img:1,input:1,ins:1,kbd:1,keygen:1,label:1,map:1,mark:1,meter:1,noscript:1,object:1,output:1,progress:1,q:1,ruby:1,s:1,samp:1,script:1,select:1,small:1,span:1,strong:1,sub:1,sup:1,textarea:1,time:1,u:1,'var':1,video:1,wbr:1 },
|
||||
// F - PF (Flow Only).
|
||||
FO = { address:1,article:1,aside:1,blockquote:1,details:1,div:1,dl:1,fieldset:1,figure:1,footer:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,header:1,hgroup:1,hr:1,menu:1,nav:1,ol:1,p:1,pre:1,section:1,table:1,ul:1 },
|
||||
// Metadata elements.
|
||||
M = { command:1,link:1,meta:1,noscript:1,script:1,style:1 },
|
||||
// Empty.
|
||||
E = {},
|
||||
// Text.
|
||||
T = { '#':1 },
|
||||
|
||||
// Deprecated phrasing elements.
|
||||
DP = { acronym:1,applet:1,basefont:1,big:1,font:1,isindex:1,strike:1,style:1,tt:1 }, // TODO remove "style".
|
||||
// Deprecated flow only elements.
|
||||
DFO = { center:1,dir:1,noframes:1 };
|
||||
|
||||
// Phrasing elements := PF + T + DP
|
||||
X( P, PF, T, DP );
|
||||
// Flow elements := FO + P + DFO
|
||||
X( F, FO, P, DFO );
|
||||
|
||||
var dtd = {
|
||||
a: Y( P, { a:1,button:1 } ), // Treat as normal inline element (not a transparent one).
|
||||
abbr: P,
|
||||
address: F,
|
||||
area: E,
|
||||
article: X( { style:1 }, F ),
|
||||
aside: X( { style:1 }, F ),
|
||||
audio: X( { source:1,track:1 }, F ),
|
||||
b: P,
|
||||
base: E,
|
||||
bdi: P,
|
||||
bdo: P,
|
||||
blockquote: F,
|
||||
body: F,
|
||||
br: E,
|
||||
button: Y( P, { a:1,button:1 } ),
|
||||
canvas: P, // Treat as normal inline element (not a transparent one).
|
||||
caption: F,
|
||||
cite: P,
|
||||
code: P,
|
||||
col: E,
|
||||
colgroup: { col:1 },
|
||||
command: E,
|
||||
datalist: X( { option:1 }, P ),
|
||||
dd: F,
|
||||
del: P, // Treat as normal inline element (not a transparent one).
|
||||
details: X( { summary:1 }, F ),
|
||||
dfn: P,
|
||||
div: X( { style:1 }, F ),
|
||||
dl: { dt:1,dd:1 },
|
||||
dt: F,
|
||||
em: P,
|
||||
embed: E,
|
||||
fieldset: X( { legend:1 }, F ),
|
||||
figcaption: F,
|
||||
figure: X( { figcaption:1 }, F ),
|
||||
footer: F,
|
||||
form: F,
|
||||
h1: P,
|
||||
h2: P,
|
||||
h3: P,
|
||||
h4: P,
|
||||
h5: P,
|
||||
h6: P,
|
||||
head: X( { title:1,base:1 }, M ),
|
||||
header: F,
|
||||
hgroup: { h1:1,h2:1,h3:1,h4:1,h5:1,h6:1 },
|
||||
hr: E,
|
||||
html: X( { head:1,body:1 }, F, M ), // Head and body are optional...
|
||||
i: P,
|
||||
iframe: T,
|
||||
img: E,
|
||||
input: E,
|
||||
ins: P, // Treat as normal inline element (not a transparent one).
|
||||
kbd: P,
|
||||
keygen: E,
|
||||
label: P,
|
||||
legend: P,
|
||||
li: F,
|
||||
link: E,
|
||||
map: F,
|
||||
mark: P, // Treat as normal inline element (not a transparent one).
|
||||
menu: X( { li:1 }, F ),
|
||||
meta: E,
|
||||
meter: Y( P, { meter:1 } ),
|
||||
nav: F,
|
||||
noscript: X( { link:1,meta:1,style:1 }, P ), // Treat as normal inline element (not a transparent one).
|
||||
object: X( { param:1 }, P ), // Treat as normal inline element (not a transparent one).
|
||||
ol: { li:1 },
|
||||
optgroup: { option:1 },
|
||||
option: T,
|
||||
output: P,
|
||||
p: P,
|
||||
param: E,
|
||||
pre: P,
|
||||
progress: Y( P, { progress:1 } ),
|
||||
q: P,
|
||||
rp: P,
|
||||
rt: P,
|
||||
ruby: X( { rp:1,rt:1 }, P ),
|
||||
s: P,
|
||||
samp: P,
|
||||
script: T,
|
||||
section: X( { style:1 }, F ),
|
||||
select: { optgroup:1,option:1 },
|
||||
small: P,
|
||||
source: E,
|
||||
span: P,
|
||||
strong: P,
|
||||
style: T,
|
||||
sub: P,
|
||||
summary: P,
|
||||
sup: P,
|
||||
table: { caption:1,colgroup:1,thead:1,tfoot:1,tbody:1,tr:1 },
|
||||
tbody: { tr:1 },
|
||||
td: F,
|
||||
textarea: T,
|
||||
tfoot: { tr:1 },
|
||||
th: F,
|
||||
thead: { tr:1 },
|
||||
time: Y( P, { time:1 } ),
|
||||
title: T,
|
||||
tr: { th:1,td:1 },
|
||||
track: E,
|
||||
u: P,
|
||||
ul: { li:1 },
|
||||
'var': P,
|
||||
video: X( { source:1,track:1 }, F ),
|
||||
wbr: E,
|
||||
|
||||
// Deprecated tags.
|
||||
acronym: P,
|
||||
applet: X( { param:1 }, F ),
|
||||
basefont: E,
|
||||
big: P,
|
||||
center: F,
|
||||
dialog: E,
|
||||
dir: { li:1 },
|
||||
font: P,
|
||||
isindex: E,
|
||||
noframes: F,
|
||||
strike: P,
|
||||
tt: P
|
||||
};
|
||||
|
||||
X( dtd, {
|
||||
/**
|
||||
* List of block elements, like `<p>` or `<div>`.
|
||||
*/
|
||||
$block: X( { audio:1,dd:1,dt:1,li:1,video:1 }, FO, DFO ),
|
||||
|
||||
/**
|
||||
* List of elements that contains other blocks, in which block-level operations should be limited,
|
||||
* this property is not intended to be checked directly, use {@link CKEDITOR.dom.elementPath#blockLimit} instead.
|
||||
*
|
||||
* Some examples of editor behaviors that are impacted by block limits:
|
||||
*
|
||||
* * Enter key never split a block-limit element;
|
||||
* * Style application is constraint by the block limit of the current selection.
|
||||
* * Pasted html will be inserted into the block limit of the current selection.
|
||||
*
|
||||
* **Note:** As an exception `<li>` is not considered as a block limit, as it's generally used as a text block.
|
||||
*/
|
||||
$blockLimit: { article:1,aside:1,audio:1,body:1,caption:1,details:1,dir:1,div:1,dl:1,fieldset:1,figure:1,footer:1,form:1,header:1,hgroup:1,menu:1,nav:1,ol:1,section:1,table:1,td:1,th:1,tr:1,ul:1,video:1 },
|
||||
|
||||
/**
|
||||
* List of elements that contain character data.
|
||||
*/
|
||||
$cdata: { script:1,style:1 },
|
||||
|
||||
/**
|
||||
* List of elements that are accepted as inline editing hosts.
|
||||
*/
|
||||
$editable: { address:1,article:1,aside:1,blockquote:1,body:1,details:1,div:1,fieldset:1,footer:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,header:1,hgroup:1,nav:1,p:1,pre:1,section:1 },
|
||||
|
||||
/**
|
||||
* List of empty (self-closing) elements, like `<br>` or `<img>`.
|
||||
*/
|
||||
$empty: { area:1,base:1,basefont:1,br:1,col:1,command:1,dialog:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,link:1,meta:1,param:1,source:1,track:1,wbr:1 },
|
||||
|
||||
/**
|
||||
* List of inline (`<span>` like) elements.
|
||||
*/
|
||||
$inline: P,
|
||||
|
||||
/**
|
||||
* List of list root elements.
|
||||
*/
|
||||
$list: { dl:1,ol:1,ul:1 },
|
||||
|
||||
/**
|
||||
* List of list item elements, like `<li>` or `<dd>`.
|
||||
*/
|
||||
$listItem: { dd:1,dt:1,li:1 },
|
||||
|
||||
/**
|
||||
* List of elements which may live outside body.
|
||||
*/
|
||||
$nonBodyContent: X( { body:1,head:1,html:1 }, dtd.head ),
|
||||
|
||||
/**
|
||||
* Elements that accept text nodes, but are not possible to edit into the browser.
|
||||
*/
|
||||
$nonEditable: { applet:1,audio:1,button:1,embed:1,iframe:1,map:1,object:1,option:1,param:1,script:1,textarea:1,video:1 },
|
||||
|
||||
/**
|
||||
* Elements that are considered objects, therefore selected as a whole in the editor.
|
||||
*/
|
||||
$object: { applet:1,audio:1,button:1,hr:1,iframe:1,img:1,input:1,object:1,select:1,table:1,textarea:1,video:1 },
|
||||
|
||||
/**
|
||||
* List of elements that can be ignored if empty, like `<b>` or `<span>`.
|
||||
*/
|
||||
$removeEmpty: { abbr:1,acronym:1,b:1,bdi: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,mark:1,meter:1,output:1,q:1,ruby:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,time:1,tt:1,u:1,'var':1 },
|
||||
|
||||
/**
|
||||
* List of elements that have tabindex set to zero by default.
|
||||
*/
|
||||
$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>`.
|
||||
*/
|
||||
$tableContent: { caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1 },
|
||||
|
||||
/**
|
||||
* List of "transparent" elements. See [W3C's definition of "transparent" element](http://dev.w3.org/html5/markup/terminology.html#transparent).
|
||||
*/
|
||||
$transparent: { a:1,audio:1,canvas:1,del:1,ins:1,map:1,noscript:1,object:1,video:1 },
|
||||
|
||||
/**
|
||||
* List of elements that are not to exist standalone that must live under it's parent element.
|
||||
*/
|
||||
$intermediate: { caption:1,colgroup:1,dd:1,dt:1,figcaption:1,legend:1,li:1,optgroup:1,option:1,rp:1,rt:1,summary:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1 }
|
||||
} );
|
||||
|
||||
return dtd;
|
||||
})();
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.dtd )
|
||||
1674
lib/ckeditor4/core/editable.js
Executable file
1674
lib/ckeditor4/core/editable.js
Executable file
File diff suppressed because it is too large
Load Diff
1554
lib/ckeditor4/core/editor.js
Executable file
1554
lib/ckeditor4/core/editor.js
Executable file
File diff suppressed because it is too large
Load Diff
36
lib/ckeditor4/core/editor_basic.js
Executable file
36
lib/ckeditor4/core/editor_basic.js
Executable file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
if ( !CKEDITOR.editor ) {
|
||||
// Documented at editor.js.
|
||||
CKEDITOR.editor = function() {
|
||||
// Push this editor to the pending list. It'll be processed later once
|
||||
// the full editor code is loaded.
|
||||
CKEDITOR._.pending.push( [ this, arguments ] );
|
||||
|
||||
// Call the CKEDITOR.event constructor to initialize this instance.
|
||||
CKEDITOR.event.call( 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.
|
||||
CKEDITOR.editor.prototype.fire = function( eventName, data ) {
|
||||
if ( eventName in { instanceReady:1,loaded:1 } )
|
||||
this[ eventName ] = true;
|
||||
|
||||
return CKEDITOR.event.prototype.fire.call( this, eventName, data, this );
|
||||
};
|
||||
|
||||
CKEDITOR.editor.prototype.fireOnce = function( eventName, data ) {
|
||||
if ( eventName in { instanceReady:1,loaded:1 } )
|
||||
this[ eventName ] = true;
|
||||
|
||||
return CKEDITOR.event.prototype.fireOnce.call( this, eventName, data, this );
|
||||
};
|
||||
|
||||
// "Inherit" (copy actually) from CKEDITOR.event.
|
||||
CKEDITOR.event.implementOn( CKEDITOR.editor.prototype );
|
||||
}
|
||||
333
lib/ckeditor4/core/env.js
Executable file
333
lib/ckeditor4/core/env.js
Executable file
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.env} object which contains
|
||||
* environment and browser information.
|
||||
*/
|
||||
|
||||
if ( !CKEDITOR.env ) {
|
||||
/**
|
||||
* Environment and browser information.
|
||||
*
|
||||
* @class CKEDITOR.env
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.env = (function() {
|
||||
var agent = navigator.userAgent.toLowerCase();
|
||||
var opera = window.opera;
|
||||
|
||||
var env = {
|
||||
/**
|
||||
* Indicates that CKEditor is running in Internet Explorer.
|
||||
*
|
||||
* if ( CKEDITOR.env.ie )
|
||||
* alert( 'I\'m running in IE!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
ie: eval( '/*@cc_on!@*/false' ),
|
||||
// Use eval to preserve conditional comment when compiling with Google Closure Compiler (#93).
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in Opera.
|
||||
*
|
||||
* if ( CKEDITOR.env.opera )
|
||||
* alert( 'I\'m running in Opera!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
opera: ( !!opera && opera.version ),
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in a WebKit-based browser, like Safari.
|
||||
*
|
||||
* if ( CKEDITOR.env.webkit )
|
||||
* alert( 'I\'m running in a WebKit browser!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
webkit: ( agent.indexOf( ' applewebkit/' ) > -1 ),
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in Adobe AIR.
|
||||
*
|
||||
* if ( CKEDITOR.env.air )
|
||||
* alert( 'I\'m on AIR!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
air: ( agent.indexOf( ' adobeair/' ) > -1 ),
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running on Macintosh.
|
||||
*
|
||||
* if ( CKEDITOR.env.mac )
|
||||
* alert( 'I love apples!'' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
mac: ( agent.indexOf( 'macintosh' ) > -1 ),
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in a Quirks Mode environemnt.
|
||||
*
|
||||
* if ( CKEDITOR.env.quirks )
|
||||
* alert( 'Nooooo!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
quirks: ( document.compatMode == 'BackCompat' ),
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in a mobile environemnt.
|
||||
*
|
||||
* if ( CKEDITOR.env.mobile )
|
||||
* alert( 'I\'m running with CKEditor today!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
mobile: ( agent.indexOf( 'mobile' ) > -1 ),
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running on Apple iPhone/iPad/iPod devices.
|
||||
*
|
||||
* if ( CKEDITOR.env.iOS )
|
||||
* alert( 'I like little apples!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
iOS: /(ipad|iphone|ipod)/.test( agent ),
|
||||
|
||||
/**
|
||||
* Indicates that the browser has a custom domain enabled. This has
|
||||
* been set with `document.domain`.
|
||||
*
|
||||
* if ( CKEDITOR.env.isCustomDomain() )
|
||||
* alert( 'I\'m in a custom domain!' );
|
||||
*
|
||||
* @returns {Boolean} `true` if a custom domain is enabled.
|
||||
* @deprecated
|
||||
*/
|
||||
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 the page is running under an encrypted connection.
|
||||
*
|
||||
* if ( CKEDITOR.env.secure )
|
||||
* alert( 'I\'m on SSL!' );
|
||||
*
|
||||
* @returns {Boolean} `true` if the page has an encrypted connection.
|
||||
*/
|
||||
secure: location.protocol == 'https:'
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in a Gecko-based browser, like
|
||||
* Firefox.
|
||||
*
|
||||
* if ( CKEDITOR.env.gecko )
|
||||
* alert( 'I\'m riding a gecko!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
env.gecko = ( navigator.product == 'Gecko' && !env.webkit && !env.opera );
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in Chrome.
|
||||
*
|
||||
* if ( CKEDITOR.env.chrome )
|
||||
* alert( 'I\'m running in Chrome!' );
|
||||
*
|
||||
* @property {Boolean} chrome
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in Safari (including the mobile version).
|
||||
*
|
||||
* if ( CKEDITOR.env.safari )
|
||||
* alert( 'I\'m on Safari!' );
|
||||
*
|
||||
* @property {Boolean} safari
|
||||
*/
|
||||
if ( env.webkit ) {
|
||||
if ( agent.indexOf( 'chrome' ) > -1 )
|
||||
env.chrome = true;
|
||||
else
|
||||
env.safari = true;
|
||||
}
|
||||
|
||||
var version = 0;
|
||||
|
||||
// Internet Explorer 6.0+
|
||||
if ( env.ie ) {
|
||||
// We use env.version for feature detection, so set it properly.
|
||||
if ( env.quirks || !document.documentMode )
|
||||
version = parseFloat( agent.match( /msie (\d+)/ )[ 1 ] );
|
||||
else
|
||||
version = document.documentMode;
|
||||
|
||||
// Deprecated features available just for backwards compatibility.
|
||||
env.ie9Compat = version == 9;
|
||||
env.ie8Compat = version == 8;
|
||||
env.ie7Compat = version == 7;
|
||||
env.ie6Compat = version < 7 || env.quirks;
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in an IE6-like environment, which
|
||||
* includes IE6 itself as well as IE7 and IE8 in Quirks Mode.
|
||||
*
|
||||
* @deprecated
|
||||
* @property {Boolean} ie6Compat
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in an IE7-like environment, which
|
||||
* includes IE7 itself and IE8's IE7 Document Mode.
|
||||
*
|
||||
* @deprecated
|
||||
* @property {Boolean} ie7Compat
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in Internet Explorer 8 on
|
||||
* Standards Mode.
|
||||
*
|
||||
* @deprecated
|
||||
* @property {Boolean} ie8Compat
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in Internet Explorer 9 on
|
||||
* Standards Mode.
|
||||
*
|
||||
* @deprecated
|
||||
* @property {Boolean} ie9Compat
|
||||
*/
|
||||
}
|
||||
|
||||
// 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 browsers (like Safari and Chrome) it contains the
|
||||
* WebKit build version (e.g. 522).
|
||||
*
|
||||
* For IE browsers, it matches the "Document Mode".
|
||||
*
|
||||
* if ( CKEDITOR.env.ie && CKEDITOR.env.version <= 6 )
|
||||
* alert( 'Ouch!' );
|
||||
*
|
||||
* @property {Number}
|
||||
*/
|
||||
env.version = version;
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in a compatible browser.
|
||||
*
|
||||
* if ( CKEDITOR.env.isCompatible )
|
||||
* alert( 'Your browser is pretty cool!' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
env.isCompatible =
|
||||
// White list of mobile devices that CKEditor supports.
|
||||
env.iOS && version >= 534 ||
|
||||
!env.mobile && (
|
||||
( env.ie && version > 6 ) ||
|
||||
( env.gecko && version >= 10801 ) ||
|
||||
( env.opera && version >= 9.5 ) ||
|
||||
( env.air && version >= 1 ) ||
|
||||
( env.webkit && version >= 522 ) ||
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* Indicates that CKEditor is running in the HiDPI environment.
|
||||
*
|
||||
* if ( CKEDITOR.env.hidpi )
|
||||
* alert( 'You are using a screen with high pixel density.' );
|
||||
*
|
||||
* @property {Boolean}
|
||||
*/
|
||||
env.hidpi = window.devicePixelRatio >= 2;
|
||||
|
||||
/**
|
||||
* A CSS class that denotes the browser where CKEditor runs and is appended
|
||||
* to the HTML element that contains the editor. It makes it easier to apply
|
||||
* browser-specific styles to editor instances.
|
||||
*
|
||||
* myDiv.className = CKEDITOR.env.cssClass;
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
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.quirks || env.version < 7 ? '6' : env.version );
|
||||
|
||||
if ( env.quirks )
|
||||
env.cssClass += ' cke_browser_iequirks';
|
||||
}
|
||||
|
||||
if ( env.gecko ) {
|
||||
if ( version < 10900 )
|
||||
env.cssClass += ' cke_browser_gecko18';
|
||||
else if ( version <= 11000 )
|
||||
env.cssClass += ' cke_browser_gecko19';
|
||||
}
|
||||
|
||||
if ( env.air )
|
||||
env.cssClass += ' cke_browser_air';
|
||||
|
||||
if ( env.iOS )
|
||||
env.cssClass += ' cke_browser_ios';
|
||||
|
||||
if ( env.hidpi )
|
||||
env.cssClass += ' cke_hidpi';
|
||||
|
||||
return env;
|
||||
})();
|
||||
}
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.env )
|
||||
// PACKAGER_RENAME( CKEDITOR.env.ie )
|
||||
387
lib/ckeditor4/core/event.js
Executable file
387
lib/ckeditor4/core/event.js
Executable file
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates an event class instance.
|
||||
*/
|
||||
CKEDITOR.event = function() {};
|
||||
|
||||
/**
|
||||
* Implements the {@link CKEDITOR.event} features in an object.
|
||||
*
|
||||
* var myObject = { message: 'Example' };
|
||||
* CKEDITOR.event.implementOn( myObject );
|
||||
*
|
||||
* myObject.on( 'testEvent', function() {
|
||||
* alert( this.message );
|
||||
* } );
|
||||
* myObject.fire( 'testEvent' ); // 'Example'
|
||||
*
|
||||
* @static
|
||||
* @param {Object} targetObject The object into which implement the features.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve the event entry on the event host (create it if needed).
|
||||
function getEntry( name ) {
|
||||
// Get the event entry (create it if needed).
|
||||
var events = getPrivate( this );
|
||||
return events[ name ] || ( events[ name ] = new eventEntry( name ) );
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Predefine some intrinsic properties on a specific event name.
|
||||
*
|
||||
* @param {String} name The event name
|
||||
* @param meta
|
||||
* @param [meta.errorProof=false] Whether the event firing should catch error thrown from a per listener call.
|
||||
*/
|
||||
define: function( name, meta ) {
|
||||
var entry = getEntry.call( this, name );
|
||||
CKEDITOR.tools.extend( entry, meta, true );
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a listener to a specific event in the current object.
|
||||
*
|
||||
* someObject.on( 'someEvent', function() {
|
||||
* alert( this == someObject ); // true
|
||||
* } );
|
||||
*
|
||||
* someObject.on( 'someEvent', function() {
|
||||
* alert( this == anotherObject ); // true
|
||||
* }, anotherObject );
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* alert( event.listenerData ); // 'Example'
|
||||
* }, null, '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
|
||||
*
|
||||
* @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=10] The listener priority. Lower priority
|
||||
* listeners are called first. Listeners with the same priority
|
||||
* value are called in registration order.
|
||||
* @returns {Object} An object containing the `removeListener`
|
||||
* function, which can be used to remove the listener at any time.
|
||||
*/
|
||||
on: function( eventName, listenerFunction, scopeObj, listenerData, priority ) {
|
||||
// Create the function to be fired for this listener.
|
||||
function listenerFirer( editor, publisherData, stopFn, cancelFn ) {
|
||||
var ev = {
|
||||
name: eventName,
|
||||
sender: this,
|
||||
editor: editor,
|
||||
data: publisherData,
|
||||
listenerData: listenerData,
|
||||
stop: stopFn,
|
||||
cancel: cancelFn,
|
||||
removeListener: removeListener
|
||||
};
|
||||
|
||||
var ret = listenerFunction.call( scopeObj, ev );
|
||||
|
||||
return ret === false ? false : ev.data;
|
||||
}
|
||||
|
||||
function removeListener() {
|
||||
me.removeListener( eventName, listenerFunction );
|
||||
}
|
||||
|
||||
var event = getEntry.call( this, 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;
|
||||
|
||||
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 { removeListener: removeListener };
|
||||
}
|
||||
}
|
||||
|
||||
// If no position has been found (or zero length), put it in
|
||||
// the front of list.
|
||||
listeners.unshift( listenerFirer );
|
||||
}
|
||||
|
||||
return { removeListener: removeListener };
|
||||
},
|
||||
|
||||
/**
|
||||
* Similiar with {@link #on} but the listener will be called only once upon the next event firing.
|
||||
*
|
||||
* @see CKEDITOR.event#on
|
||||
*/
|
||||
once: function() {
|
||||
var fn = arguments[ 1 ];
|
||||
|
||||
arguments[ 1 ] = function( evt ) {
|
||||
evt.removeListener();
|
||||
return fn.apply( this, arguments );
|
||||
};
|
||||
|
||||
return this.on.apply( this, arguments );
|
||||
},
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @property {Boolean} useCapture
|
||||
* @todo
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register event handler under the capturing stage on supported target.
|
||||
*/
|
||||
capture: function() {
|
||||
CKEDITOR.event.useCapture = 1;
|
||||
var retval = this.on.apply( this, arguments );
|
||||
CKEDITOR.event.useCapture = 0;
|
||||
return retval;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fires an specific event in the object. All registered listeners are
|
||||
* called at this point.
|
||||
*
|
||||
* someObject.on( 'someEvent', function() { ... } );
|
||||
* someObject.on( 'someEvent', function() { ... } );
|
||||
* someObject.fire( 'someEvent' ); // Both listeners are called.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* alert( event.data ); // 'Example'
|
||||
* } );
|
||||
* someObject.fire( 'someEvent', 'Example' );
|
||||
*
|
||||
* @method
|
||||
* @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 boolean indicating that the event is to be
|
||||
* canceled, or data returned by one of the listeners.
|
||||
*/
|
||||
fire: (function() {
|
||||
// Create the function that marks the event as stopped.
|
||||
var stopped = 0;
|
||||
var stopEvent = function() {
|
||||
stopped = 1;
|
||||
};
|
||||
|
||||
// Create the function that marks the event as canceled.
|
||||
var canceled = 0;
|
||||
var cancelEvent = function() {
|
||||
canceled = 1;
|
||||
};
|
||||
|
||||
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 = 0;
|
||||
|
||||
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 );
|
||||
|
||||
var retData;
|
||||
// Loop through all listeners.
|
||||
for ( var i = 0; i < listeners.length; i++ ) {
|
||||
// Call the listener, passing the event data.
|
||||
if ( event.errorProof ) {
|
||||
try {
|
||||
retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent );
|
||||
} catch ( er ) {}
|
||||
} else
|
||||
retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent );
|
||||
|
||||
if ( retData === false )
|
||||
canceled = 1;
|
||||
else if ( typeof retData != 'undefined' )
|
||||
data = retData;
|
||||
|
||||
// No further calls is stopped or canceled.
|
||||
if ( stopped || canceled )
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ret = canceled ? false : ( typeof data == 'undefined' ? true : 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}.
|
||||
*
|
||||
* someObject.on( 'someEvent', function() { ... } );
|
||||
* someObject.fire( 'someEvent' ); // Above listener called.
|
||||
* someObject.fireOnce( 'someEvent' ); // Above listener called.
|
||||
* someObject.fire( 'someEvent' ); // No listeners called.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* var myListener = function() { ... };
|
||||
* someObject.on( 'someEvent', myListener );
|
||||
* someObject.fire( 'someEvent' ); // myListener called.
|
||||
* someObject.removeListener( 'someEvent', myListener );
|
||||
* someObject.fire( 'someEvent' ); // myListener not called.
|
||||
*
|
||||
* @param {String} eventName The event name.
|
||||
* @param {Function} listenerFunction The listener function to unregister.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all existing listeners on this object, for cleanup purpose.
|
||||
*/
|
||||
removeAllListeners: function() {
|
||||
var events = getPrivate( this );
|
||||
for ( var i in events )
|
||||
delete events[ i ];
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if there is any listener registered to a given event.
|
||||
*
|
||||
* var myListener = function() { ... };
|
||||
* someObject.on( 'someEvent', myListener );
|
||||
* alert( someObject.hasListeners( 'someEvent' ) ); // true
|
||||
* alert( someObject.hasListeners( 'noEvent' ) ); // false
|
||||
*
|
||||
* @param {String} eventName The event name.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasListeners: function( eventName ) {
|
||||
var event = getPrivate( this )[ eventName ];
|
||||
return ( event && event.listeners.length > 0 );
|
||||
}
|
||||
};
|
||||
})();
|
||||
}
|
||||
115
lib/ckeditor4/core/eventInfo.js
Executable file
115
lib/ckeditor4/core/eventInfo.js
Executable file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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 that illustrates the features of the event object to be
|
||||
* passed to event listeners by a {@link CKEDITOR.event} based object.
|
||||
*
|
||||
* This class is not really part of the API.
|
||||
*
|
||||
* @class CKEDITOR.eventInfo
|
||||
* @abstract
|
||||
*/
|
||||
|
||||
/**
|
||||
* The event name.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* alert( event.name ); // 'someEvent'
|
||||
* } );
|
||||
* someObject.fire( 'someEvent' );
|
||||
*
|
||||
* @property {String} name
|
||||
*/
|
||||
|
||||
/**
|
||||
* The object that publishes (sends) the event.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* alert( event.sender == someObject ); // true
|
||||
* } );
|
||||
* someObject.fire( 'someEvent' );
|
||||
*
|
||||
* @property sender
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* myButton.on( 'someEvent', function( event ) {
|
||||
* alert( event.editor == myEditor ); // true
|
||||
* } );
|
||||
* myButton.fire( 'someEvent', null, myEditor );
|
||||
*
|
||||
* @property {CKEDITOR.editor} editor
|
||||
*/
|
||||
|
||||
/**
|
||||
* Any kind of additional data. Its format and usage is event dependent.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* alert( event.data ); // 'Example'
|
||||
* } );
|
||||
* someObject.fire( 'someEvent', 'Example' );
|
||||
*
|
||||
* @property data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Any extra data appended during the listener registration.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* alert( event.listenerData ); // 'Example'
|
||||
* }, null, 'Example' );
|
||||
*
|
||||
* @property listenerData
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that no further listeners are to be called.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* event.stop();
|
||||
* } );
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* // This one will not be called.
|
||||
* } );
|
||||
* alert( someObject.fire( 'someEvent' ) ); // false
|
||||
*
|
||||
* @method stop
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that the event is to be cancelled (if cancelable).
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* event.cancel();
|
||||
* } );
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* // This one will not be called.
|
||||
* } );
|
||||
* alert( someObject.fire( 'someEvent' ) ); // true
|
||||
*
|
||||
* @method cancel
|
||||
*/
|
||||
|
||||
/**
|
||||
* Removes the current listener.
|
||||
*
|
||||
* someObject.on( 'someEvent', function( event ) {
|
||||
* event.removeListener();
|
||||
* // Now this function won't be called again by 'someEvent'.
|
||||
* } );
|
||||
*
|
||||
* @method removeListener
|
||||
*/
|
||||
2057
lib/ckeditor4/core/filter.js
Executable file
2057
lib/ckeditor4/core/filter.js
Executable file
File diff suppressed because it is too large
Load Diff
253
lib/ckeditor4/core/focusmanager.js
Executable file
253
lib/ckeditor4/core/focusmanager.js
Executable file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.focusManager} class, which is used
|
||||
* to handle the focus on editor instances..
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* var focusManager = new CKEDITOR.focusManager( editor );
|
||||
* focusManager.focus();
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a focusManager class instance.
|
||||
* @param {CKEDITOR.editor} editor The editor instance.
|
||||
*/
|
||||
CKEDITOR.focusManager = function( editor ) {
|
||||
if ( editor.focusManager )
|
||||
return editor.focusManager;
|
||||
|
||||
/**
|
||||
* Indicates that the editor instance has focus.
|
||||
*
|
||||
* alert( CKEDITOR.instances.editor1.focusManager.hasFocus ); // e.g. true
|
||||
*/
|
||||
this.hasFocus = false;
|
||||
|
||||
/**
|
||||
* Indicate the currently focused DOM element that makes the editor activated.
|
||||
*
|
||||
* @property {CKEDITOR.dom.domObject}
|
||||
*/
|
||||
this.currentActive = null;
|
||||
|
||||
/**
|
||||
* Object used to hold private stuff.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this._ = {
|
||||
editor: editor
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
var SLOT_NAME = 'focusmanager',
|
||||
SLOT_NAME_LISTENERS = 'focusmanager_handlers';
|
||||
|
||||
CKEDITOR.focusManager._ = {
|
||||
/**
|
||||
* The delay (in milliseconds) to deactivate the editor when UI dom element has lost focus.
|
||||
*
|
||||
* @private
|
||||
* @static
|
||||
* @property {Number} [_.blurDelay=200]
|
||||
*/
|
||||
blurDelay: 200
|
||||
};
|
||||
|
||||
CKEDITOR.focusManager.prototype = {
|
||||
|
||||
/**
|
||||
* Indicate this editor instance is activated (due to DOM focus change),
|
||||
* the `activated` state is a symbolic indicator of an active user
|
||||
* interaction session.
|
||||
*
|
||||
* **Note:** This method will not introduce UI focus
|
||||
* impact on DOM, it's here to record editor UI focus state internally.
|
||||
* If you want to make the cursor blink inside of the editable, use
|
||||
* {@link CKEDITOR.editor#method-focus} instead.
|
||||
*
|
||||
* var editor = CKEDITOR.instances.editor1;
|
||||
* editor.focusManage.focus( editor.editable() );
|
||||
*/
|
||||
focus: function() {
|
||||
if ( this._.timer )
|
||||
clearTimeout( this._.timer );
|
||||
|
||||
if ( ! ( this.hasFocus || this._.locked ) ) {
|
||||
// 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"
|
||||
var current = CKEDITOR.currentInstance;
|
||||
current && current.focusManager.blur( 1 );
|
||||
|
||||
this.hasFocus = true;
|
||||
|
||||
var ct = this._.editor.container;
|
||||
ct && ct.addClass( 'cke_focus' );
|
||||
this._.editor.fire( 'focus' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Prevent from changing the focus manager state until next {@link #unlock} is called.
|
||||
*/
|
||||
lock: function() {
|
||||
this._.locked = 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore the automatic focus management, if {@link #lock} is called.
|
||||
*/
|
||||
unlock: function() {
|
||||
delete this._.locked;
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to indicate that the editor instance has been deactivated by the specified
|
||||
* element which has just lost focus.
|
||||
*
|
||||
* **Note:** that this functions acts asynchronously with a delay of 100ms to
|
||||
* avoid temporary deactivation. Use instead the `noDelay` parameter
|
||||
* to deactivate immediately.
|
||||
*
|
||||
* var editor = CKEDITOR.instances.editor1;
|
||||
* editor.focusManager.blur();
|
||||
*
|
||||
* @param {Boolean} [noDelay=false] Deactivate immediately the editor instance synchronously.
|
||||
*/
|
||||
blur: function( noDelay ) {
|
||||
if ( this._.locked )
|
||||
return;
|
||||
|
||||
function doBlur() {
|
||||
if ( this.hasFocus ) {
|
||||
this.hasFocus = false;
|
||||
|
||||
var ct = this._.editor.container;
|
||||
ct && ct.removeClass( 'cke_focus' );
|
||||
this._.editor.fire( 'blur' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( this._.timer )
|
||||
clearTimeout( this._.timer );
|
||||
|
||||
var delay = CKEDITOR.focusManager._.blurDelay;
|
||||
if ( noDelay || !delay ) {
|
||||
doBlur.call( this );
|
||||
} else {
|
||||
this._.timer = CKEDITOR.tools.setTimeout( function() {
|
||||
delete this._.timer;
|
||||
doBlur.call( this );
|
||||
}, delay, this );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Register an UI DOM element to the focus manager, which will make the focus manager "hasFocus"
|
||||
* once input focus is relieved on the element, it's to be used by plugins to expand the jurisdiction of the editor focus.
|
||||
*
|
||||
* @param {CKEDITOR.dom.element} element The container (top most) element of one UI part.
|
||||
* @param {Boolean} isCapture If specified {@link CKEDITOR.event#useCapture} will be used when listening to the focus event.
|
||||
*/
|
||||
add: function( element, isCapture ) {
|
||||
var fm = element.getCustomData( SLOT_NAME );
|
||||
if ( !fm || fm != this ) {
|
||||
// If this element is already taken by another instance, dismiss it first.
|
||||
fm && fm.remove( element );
|
||||
|
||||
var focusEvent = 'focus',
|
||||
blurEvent = 'blur';
|
||||
|
||||
// Bypass the element's internal DOM focus change.
|
||||
if ( isCapture ) {
|
||||
|
||||
// Use "focusin/focusout" events instead of capture phase in IEs,
|
||||
// which fires synchronously.
|
||||
if ( CKEDITOR.env.ie ) {
|
||||
focusEvent = 'focusin';
|
||||
blurEvent = 'focusout';
|
||||
} else
|
||||
CKEDITOR.event.useCapture = 1;
|
||||
}
|
||||
|
||||
var listeners = {
|
||||
blur: function() {
|
||||
if ( element.equals( this.currentActive ) )
|
||||
this.blur();
|
||||
},
|
||||
focus: function() {
|
||||
this.currentActive = element;
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
element.on( focusEvent, listeners.focus, this );
|
||||
element.on( blurEvent, listeners.blur, this );
|
||||
|
||||
if ( isCapture )
|
||||
CKEDITOR.event.useCapture = 0;
|
||||
|
||||
element.setCustomData( SLOT_NAME, this );
|
||||
element.setCustomData( SLOT_NAME_LISTENERS, listeners );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss an element from the the focus manager delegations added by {@link #add}.
|
||||
*
|
||||
* @param {CKEDITOR.dom.element} element The element to be removed from the focusmanager.
|
||||
*/
|
||||
remove: function( element ) {
|
||||
element.removeCustomData( SLOT_NAME );
|
||||
var listeners = element.removeCustomData( SLOT_NAME_LISTENERS );
|
||||
element.removeListener( 'blur', listeners.blur );
|
||||
element.removeListener( 'focus', listeners.focus );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
/**
|
||||
* Fired when the editor instance receives the input focus.
|
||||
*
|
||||
* editor.on( 'focus', function( e ) {
|
||||
* alert( 'The editor named ' + e.editor.name + ' is now focused' );
|
||||
* } );
|
||||
*
|
||||
* @event focus
|
||||
* @member CKEDITOR.editor
|
||||
* @param {CKEDITOR.editor} editor The editor instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the editor instance loses the input focus.
|
||||
*
|
||||
* **Note:** This event will **NOT** be triggered when focus is moved internally, e.g. from
|
||||
* the editable to other part of the editor UI like dialog.
|
||||
* If you're interested on only the editable focus state listen to the {@link CKEDITOR.editable#event-focus}
|
||||
* and {@link CKEDITOR.editable#blur} events instead.
|
||||
*
|
||||
* editor.on( 'blur', function( e ) {
|
||||
* alert( 'The editor named ' + e.editor.name + ' lost the focus' );
|
||||
* } );
|
||||
*
|
||||
* @event blur
|
||||
* @member CKEDITOR.editor
|
||||
* @param {CKEDITOR.editor} editor The editor instance.
|
||||
*/
|
||||
860
lib/ckeditor4/core/htmldataprocessor.js
Executable file
860
lib/ckeditor4/core/htmldataprocessor.js
Executable file
@@ -0,0 +1,860 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* Represents an HTML data processor, which is responsible for translating and
|
||||
* transforming the editor data on input and output.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.dataProcessor
|
||||
* @constructor Creates an htmlDataProcessor class instance.
|
||||
* @param {CKEDITOR.editor} editor
|
||||
*/
|
||||
CKEDITOR.htmlDataProcessor = function( editor ) {
|
||||
var dataFilter, htmlFilter,
|
||||
that = this;
|
||||
|
||||
this.editor = editor;
|
||||
|
||||
this.dataFilter = dataFilter = new CKEDITOR.htmlParser.filter();
|
||||
this.htmlFilter = htmlFilter = new CKEDITOR.htmlParser.filter();
|
||||
|
||||
/**
|
||||
* The HTML writer used by this data processor to format the output.
|
||||
*
|
||||
* @property {CKEDITOR.htmlParser.basicWriter}
|
||||
*/
|
||||
this.writer = new CKEDITOR.htmlParser.basicWriter();
|
||||
|
||||
dataFilter.addRules( defaultDataFilterRules );
|
||||
dataFilter.addRules( createBogusAndFillerRules( editor, 'data' ) );
|
||||
htmlFilter.addRules( defaultHtmlFilterRules );
|
||||
htmlFilter.addRules( createBogusAndFillerRules( editor, 'html' ) );
|
||||
|
||||
editor.on( 'toHtml', function( evt ) {
|
||||
var evtData = evt.data,
|
||||
data = evtData.dataValue;
|
||||
|
||||
// The source data is already HTML, but we need to clean
|
||||
// it up and apply the filter.
|
||||
data = protectSource( data, editor );
|
||||
|
||||
// Protect content of textareas. (#9995)
|
||||
// Do this before protecting attributes to avoid breaking:
|
||||
// <textarea><img src="..." /></textarea>
|
||||
data = protectElements( data, protectTextareaRegex );
|
||||
|
||||
// Before anything, we must protect the URL attributes as the
|
||||
// browser may changing them when setting the innerHTML later in
|
||||
// the code.
|
||||
data = protectAttributes( data );
|
||||
|
||||
// Protect elements than can't be set inside a DIV. E.g. IE removes
|
||||
// style tags from innerHTML. (#3710)
|
||||
data = protectElements( data, protectElementsRegex );
|
||||
|
||||
// Certain elements has problem to go through DOM operation, protect
|
||||
// them by prefixing 'cke' namespace. (#3591)
|
||||
data = protectElementsNames( data );
|
||||
|
||||
// All none-IE browsers ignore self-closed custom elements,
|
||||
// protecting them into open-close. (#3591)
|
||||
data = protectSelfClosingElements( data );
|
||||
|
||||
// Compensate one leading line break after <pre> open as browsers
|
||||
// eat it up. (#5789)
|
||||
data = protectPreFormatted( data );
|
||||
|
||||
var fixBin = evtData.context || editor.editable().getName(),
|
||||
isPre;
|
||||
|
||||
// Old IEs loose formats when load html into <pre>.
|
||||
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && fixBin == 'pre' ) {
|
||||
fixBin = 'div';
|
||||
data = '<pre>' + data + '</pre>';
|
||||
isPre = 1;
|
||||
}
|
||||
|
||||
// Call the browser to help us fixing a possibly invalid HTML
|
||||
// structure.
|
||||
var el = editor.document.createElement( fixBin );
|
||||
// Add fake character to workaround IE comments bug. (#3801)
|
||||
el.setHtml( 'a' + data );
|
||||
data = el.getHtml().substr( 1 );
|
||||
|
||||
// Restore shortly protected attribute names.
|
||||
data = data.replace( new RegExp( ' data-cke-' + CKEDITOR.rnd + '-', 'ig' ), ' ' );
|
||||
|
||||
isPre && ( data = data.replace( /^<pre>|<\/pre>$/gi, '' ) );
|
||||
|
||||
// Unprotect "some" of the protected elements at this point.
|
||||
data = unprotectElementNames( data );
|
||||
|
||||
data = unprotectElements( data );
|
||||
|
||||
// Restore the comments that have been protected, in this way they
|
||||
// can be properly filtered.
|
||||
data = unprotectRealComments( data );
|
||||
|
||||
// Now use our parser to make further fixes to the structure, as
|
||||
// well as apply the filter.
|
||||
evtData.dataValue = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, evtData.fixForBody === false ? false : getFixBodyTag( editor.config ) );
|
||||
}, null, null, 5 );
|
||||
|
||||
editor.on( 'toHtml', function( evt ) {
|
||||
evt.data.dataValue.filterChildren( that.dataFilter, true );
|
||||
}, null, null, 10 );
|
||||
|
||||
editor.on( 'toHtml', function( evt ) {
|
||||
var evtData = evt.data,
|
||||
data = evtData.dataValue,
|
||||
writer = new CKEDITOR.htmlParser.basicWriter();
|
||||
|
||||
data.writeChildrenHtml( writer );
|
||||
data = writer.getHtml( true );
|
||||
|
||||
// Protect the real comments again.
|
||||
evtData.dataValue = protectRealComments( data );
|
||||
}, null, null, 15 );
|
||||
|
||||
|
||||
editor.on( 'toDataFormat', function( evt ) {
|
||||
evt.data.dataValue = CKEDITOR.htmlParser.fragment.fromHtml(
|
||||
evt.data.dataValue, editor.editable().getName(), getFixBodyTag( editor.config ) );
|
||||
}, null, null, 5 );
|
||||
|
||||
editor.on( 'toDataFormat', function( evt ) {
|
||||
evt.data.dataValue.filterChildren( that.htmlFilter, true );
|
||||
}, null, null, 10 );
|
||||
|
||||
editor.on( 'toDataFormat', function( evt ) {
|
||||
var data = evt.data.dataValue,
|
||||
writer = that.writer;
|
||||
|
||||
writer.reset();
|
||||
data.writeChildrenHtml( writer );
|
||||
data = writer.getHtml( true );
|
||||
|
||||
// Restore those non-HTML protected source. (#4475,#4880)
|
||||
data = unprotectRealComments( data );
|
||||
data = unprotectSource( data, editor );
|
||||
|
||||
evt.data.dataValue = data;
|
||||
}, null, null, 15 );
|
||||
};
|
||||
|
||||
CKEDITOR.htmlDataProcessor.prototype = {
|
||||
/**
|
||||
* Processes the input (potentially malformed) HTML to a purified form which
|
||||
* is suitable for using in the WYSIWYG editable.
|
||||
*
|
||||
* @param {String} data The raw data.
|
||||
* @param {String} [context] The tag name of a context element within which
|
||||
* the input is to be processed, default to be the editable element.
|
||||
* If `null` is passed, then data will be parsed without context (as children of {@link CKEDITOR.htmlParser.fragment}).
|
||||
* See {@link CKEDITOR.htmlParser.fragment#fromHtml} for more details.
|
||||
* @param {Boolean} [fixForBody] Whether to trigger the auto paragraph for non-block contents.
|
||||
* @param {Boolean} [dontFilter] Do not filter data with {@link CKEDITOR.filter}.
|
||||
* @returns {String}
|
||||
*/
|
||||
toHtml: function( data, context, fixForBody, dontFilter ) {
|
||||
var editor = this.editor;
|
||||
|
||||
// Fall back to the editable as context if not specified.
|
||||
if ( !context && context !== null )
|
||||
context = editor.editable().getName();
|
||||
|
||||
return editor.fire( 'toHtml', {
|
||||
dataValue: data,
|
||||
context: context,
|
||||
fixForBody: fixForBody,
|
||||
dontFilter: !!dontFilter
|
||||
} ).dataValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* See {@link CKEDITOR.dataProcessor#toDataFormat}.
|
||||
*
|
||||
* @param {String} html
|
||||
* @returns {String}
|
||||
*/
|
||||
toDataFormat: function( html ) {
|
||||
return this.editor.fire( 'toDataFormat', {
|
||||
dataValue: html
|
||||
} ).dataValue;
|
||||
}
|
||||
};
|
||||
|
||||
// Produce a set of filtering rules that handles bogus and filler node at the
|
||||
// end of block/pseudo block, in the following consequence:
|
||||
// 1. elements:<block> - this filter removes any bogus node, then check
|
||||
// if it's an empty block that requires a filler.
|
||||
// 2. elements:<br> - After cleaned with bogus, this filter checks the real
|
||||
// line-break BR to compensate a filler after it.
|
||||
//
|
||||
// Terms definitions:
|
||||
// filler: An element that's either <BR> or &NBSP; at the end of block that established line height.
|
||||
// bogus: Whenever a filler is proceeded with inline content, it becomes a bogus which is subjected to be removed.
|
||||
//
|
||||
// Various forms of the filler:
|
||||
// In output HTML: Filler should be consistently &NBSP; <BR> at the end of block is always considered as bogus.
|
||||
// In Wysiwyg HTML: Browser dependent - Filler is either BR for non-IE, or &NBSP; for IE, <BR> is NEVER considered as bogus for IE.
|
||||
function createBogusAndFillerRules( editor, type ) {
|
||||
function createFiller( isOutput ) {
|
||||
return isOutput || CKEDITOR.env.ie ?
|
||||
new CKEDITOR.htmlParser.text( '\xa0' ) :
|
||||
new CKEDITOR.htmlParser.element( 'br', { 'data-cke-bogus': 1 } );
|
||||
}
|
||||
|
||||
// This text block filter, remove any bogus and create the filler on demand.
|
||||
function blockFilter( isOutput, fillEmptyBlock ) {
|
||||
|
||||
return function( block ) {
|
||||
|
||||
// DO NOT apply the filer if it's a fragment node.
|
||||
if ( block.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT )
|
||||
return;
|
||||
|
||||
cleanBogus( block );
|
||||
|
||||
// [Opera] it's mandatory for the filler to present inside of empty block when in WYSIWYG.
|
||||
if ( ( ( CKEDITOR.env.opera && !isOutput ) ||
|
||||
( typeof fillEmptyBlock == 'function' ? fillEmptyBlock( block ) !== false : fillEmptyBlock ) ) &&
|
||||
isEmptyBlockNeedFiller( block ) )
|
||||
{
|
||||
block.add( createFiller( isOutput ) );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Append a filler right after the last line-break BR, found at the end of block.
|
||||
function brFilter( isOutput ) {
|
||||
return function ( br ) {
|
||||
|
||||
// DO NOT apply the filer if parent's a fragment node.
|
||||
if ( br.parent.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT )
|
||||
return;
|
||||
|
||||
var attrs = br.attributes;
|
||||
// Dismiss BRs that are either bogus or eol marker.
|
||||
if ( 'data-cke-bogus' in attrs ||
|
||||
'data-cke-eol' in attrs ) {
|
||||
delete attrs [ 'data-cke-bogus' ];
|
||||
return;
|
||||
}
|
||||
|
||||
// Judge the tail line-break BR, and to insert bogus after it.
|
||||
var next = getNext( br ), previous = getPrevious( br );
|
||||
|
||||
if ( !next && isBlockBoundary( br.parent ) )
|
||||
append( br.parent, createFiller( isOutput ) );
|
||||
else if ( isBlockBoundary( next ) && previous && !isBlockBoundary( previous ) ) {
|
||||
insertBefore( next, createFiller( isOutput ) );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Determinate whether this node is potentially a bogus node.
|
||||
function maybeBogus( node, atBlockEnd ) {
|
||||
|
||||
// BR that's not from IE DOM, except for a EOL marker.
|
||||
if ( !( isOutput && CKEDITOR.env.ie ) &&
|
||||
node.type == CKEDITOR.NODE_ELEMENT && node.name == 'br' &&
|
||||
!node.attributes[ 'data-cke-eol' ] )
|
||||
return true;
|
||||
|
||||
var match;
|
||||
// NBSP, possibly.
|
||||
if ( node.type == CKEDITOR.NODE_TEXT &&
|
||||
( match = node.value.match( tailNbspRegex ) ) )
|
||||
{
|
||||
// We need to separate tail NBSP out of a text node, for later removal.
|
||||
if ( match.index ) {
|
||||
insertBefore( node, new CKEDITOR.htmlParser.text( node.value.substring( 0, match.index ) ) );
|
||||
node.value = match[ 0 ];
|
||||
}
|
||||
|
||||
// From IE DOM, at the end of a text block, or before block boundary.
|
||||
if ( CKEDITOR.env.ie && isOutput && ( !atBlockEnd || node.parent.name in textBlockTags ) )
|
||||
return true;
|
||||
|
||||
// From the output.
|
||||
if ( !isOutput ) {
|
||||
var previous = node.previous;
|
||||
|
||||
// Following a line-break at the end of block.
|
||||
if ( previous && previous.name == 'br' )
|
||||
return true;
|
||||
|
||||
// Or a single NBSP between two blocks.
|
||||
if ( !previous || isBlockBoundary( previous ) )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Removes all bogus inside of this block, and to convert fillers into the proper form.
|
||||
function cleanBogus( block ) {
|
||||
var bogus = [];
|
||||
var last = getLast( block ), node, previous;
|
||||
if ( last ) {
|
||||
|
||||
// Check for bogus at the end of this block.
|
||||
// e.g. <p>foo<br /></p>
|
||||
maybeBogus( last, 1 ) && bogus.push( last );
|
||||
|
||||
while ( last ) {
|
||||
|
||||
// Check for bogus at the end of any pseudo block contained.
|
||||
if ( isBlockBoundary( last ) &&
|
||||
( node = getPrevious( last ) ) &&
|
||||
maybeBogus( node ) )
|
||||
{
|
||||
// Bogus must have inline proceeding, instead single BR between two blocks,
|
||||
// is considered as filler, e.g. <hr /><br /><hr />
|
||||
if ( ( previous = getPrevious( node ) ) && !isBlockBoundary( previous ) )
|
||||
bogus.push( node );
|
||||
// Convert the filler into appropriate form.
|
||||
else {
|
||||
insertAfter( node, createFiller( isOutput ) );
|
||||
removeFromParent( node );
|
||||
}
|
||||
}
|
||||
|
||||
last = last.previous;
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove all bogus collected from above.
|
||||
for ( var i = 0 ; i < bogus.length ; i++ )
|
||||
removeFromParent( bogus[ i ] );
|
||||
}
|
||||
|
||||
// Judge whether it's an empty block that requires a filler node.
|
||||
function isEmptyBlockNeedFiller( block ) {
|
||||
|
||||
// DO NOT fill empty editable in IE.
|
||||
if ( !isOutput && CKEDITOR.env.ie && block.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT )
|
||||
return false;
|
||||
|
||||
// 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg;
|
||||
// 2. For the rest, at least table cell and list item need no filler space. (#6248)
|
||||
if ( !isOutput && CKEDITOR.env.ie &&
|
||||
( document.documentMode > 7 ||
|
||||
block.name in CKEDITOR.dtd.tr ||
|
||||
block.name in CKEDITOR.dtd.$listItem ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var last = getLast( block );
|
||||
return !last || block.name == 'form' && last.name == 'input' ;
|
||||
}
|
||||
|
||||
var rules = { elements: {} };
|
||||
var isOutput = type == 'html';
|
||||
|
||||
// Build the list of text blocks.
|
||||
var textBlockTags = CKEDITOR.tools.extend( {}, blockLikeTags );
|
||||
for ( var i in textBlockTags ) {
|
||||
if ( !( '#' in dtd[ i ] ) )
|
||||
delete textBlockTags[ i ];
|
||||
}
|
||||
|
||||
for ( i in textBlockTags )
|
||||
rules.elements[ i ] = blockFilter( isOutput, editor.config.fillEmptyBlocks !== false );
|
||||
|
||||
// Editable element is to be checked separately.
|
||||
rules.root = blockFilter( isOutput );
|
||||
rules.elements.br = brFilter( isOutput );
|
||||
return rules;
|
||||
}
|
||||
|
||||
function getFixBodyTag( config ) {
|
||||
return ( config.enterMode != CKEDITOR.ENTER_BR && config.autoParagraph !== false ) ? config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false;
|
||||
}
|
||||
|
||||
// Regex to scan for at the end of blocks, which are actually placeholders.
|
||||
// Safari transforms the to \xa0. (#4172)
|
||||
var tailNbspRegex = /(?: |\xa0)$/;
|
||||
|
||||
var protectedSourceMarker = '{cke_protected}';
|
||||
|
||||
function getLast( node ) {
|
||||
var last = node.children[ node.children.length - 1 ];
|
||||
while ( last && isEmpty( last ) )
|
||||
last = last.previous;
|
||||
return last;
|
||||
}
|
||||
|
||||
function getNext( node ) {
|
||||
var next = node.next;
|
||||
while ( next && isEmpty( next ) )
|
||||
next = next.next;
|
||||
return next;
|
||||
}
|
||||
|
||||
function getPrevious( node ) {
|
||||
var previous = node.previous;
|
||||
while ( previous && isEmpty( previous ) )
|
||||
previous = previous.previous;
|
||||
return previous;
|
||||
}
|
||||
|
||||
// Judge whether the node is an ghost node to be ignored, when traversing.
|
||||
function isEmpty( node ) {
|
||||
return node.type == CKEDITOR.NODE_TEXT &&
|
||||
!CKEDITOR.tools.trim( node.value ) ||
|
||||
node.type == CKEDITOR.NODE_ELEMENT &&
|
||||
node.attributes[ 'data-cke-bookmark' ];
|
||||
}
|
||||
|
||||
// Judge whether the node is a block-like element.
|
||||
function isBlockBoundary( node ) {
|
||||
return node &&
|
||||
( node.type == CKEDITOR.NODE_ELEMENT && node.name in blockLikeTags ||
|
||||
node.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT );
|
||||
}
|
||||
|
||||
function insertAfter( node, insertion ) {
|
||||
var children = node.parent.children;
|
||||
var index = CKEDITOR.tools.indexOf( children, node );
|
||||
children.splice( index + 1, 0, insertion );
|
||||
var next = node.next;
|
||||
node.next = insertion;
|
||||
insertion.previous = node;
|
||||
insertion.parent = node.parent;
|
||||
insertion.next = next;
|
||||
}
|
||||
|
||||
function insertBefore( node, insertion ) {
|
||||
var children = node.parent.children;
|
||||
var index = CKEDITOR.tools.indexOf( children, node );
|
||||
children.splice( index, 0, insertion );
|
||||
var prev = node.previous;
|
||||
node.previous = insertion;
|
||||
insertion.next = node;
|
||||
insertion.parent = node.parent;
|
||||
if ( prev ) {
|
||||
insertion.previous = prev;
|
||||
prev.next = insertion;
|
||||
}
|
||||
}
|
||||
|
||||
function append( parent, node ) {
|
||||
var last = parent.children[ parent.children.length -1 ];
|
||||
parent.children.push( node );
|
||||
node.parent = parent;
|
||||
if ( last ) {
|
||||
last.next = node;
|
||||
node.previous = last;
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromParent( node ) {
|
||||
var children = node.parent.children;
|
||||
var index = CKEDITOR.tools.indexOf( children, node );
|
||||
var previous = node.previous, next = node.next;
|
||||
previous && ( previous.next = next );
|
||||
next && ( next.previous = previous );
|
||||
children.splice( index, 1 );
|
||||
}
|
||||
|
||||
function getNodeIndex( node ) {
|
||||
var parent = node.parent;
|
||||
return parent ? CKEDITOR.tools.indexOf( parent.children, node ) : -1;
|
||||
}
|
||||
|
||||
var dtd = CKEDITOR.dtd;
|
||||
|
||||
// Define orders of table elements.
|
||||
var tableOrder = [ 'caption', 'colgroup', 'col', 'thead', 'tfoot', 'tbody' ];
|
||||
|
||||
// List of all block elements.
|
||||
var blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$blockLimit, dtd.$block );
|
||||
|
||||
var defaultDataFilterRules = {
|
||||
elements: {},
|
||||
attributeNames: [
|
||||
// Event attributes (onXYZ) must not be directly set. They can become
|
||||
// active in the editing area (IE|WebKit).
|
||||
[ ( /^on/ ), 'data-cke-pa-on' ]
|
||||
]
|
||||
};
|
||||
|
||||
var defaultHtmlFilterRules = {
|
||||
elementNames: [
|
||||
// Remove the "cke:" namespace prefix.
|
||||
[ ( /^cke:/ ), '' ],
|
||||
|
||||
// Ignore <?xml:namespace> tags.
|
||||
[ ( /^\?xml:namespace$/ ), '' ]
|
||||
],
|
||||
|
||||
attributeNames: [
|
||||
// Attributes saved for changes and protected attributes.
|
||||
[ ( /^data-cke-(saved|pa)-/ ), '' ],
|
||||
|
||||
// All "data-cke-" attributes are to be ignored.
|
||||
[ ( /^data-cke-.*/ ), '' ],
|
||||
|
||||
[ 'hidefocus', '' ]
|
||||
],
|
||||
|
||||
elements: {
|
||||
$: function( element ) {
|
||||
var attribs = element.attributes;
|
||||
|
||||
if ( attribs ) {
|
||||
// Elements marked as temporary are to be ignored.
|
||||
if ( attribs[ 'data-cke-temp' ] )
|
||||
return false;
|
||||
|
||||
// Remove duplicated attributes - #3789.
|
||||
var attributeNames = [ 'name', 'href', 'src' ],
|
||||
savedAttributeName;
|
||||
for ( var i = 0; i < attributeNames.length; i++ ) {
|
||||
savedAttributeName = 'data-cke-saved-' + attributeNames[ i ];
|
||||
savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] );
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
// The contents of table should be in correct order (#4809).
|
||||
table: function( element ) {
|
||||
// Clone the array as it would become empty during the sort call.
|
||||
var children = element.children.slice( 0 );
|
||||
children.sort( function ( node1, node2 ) {
|
||||
var index1, index2;
|
||||
|
||||
// Compare in the predefined order.
|
||||
if ( node1.type == CKEDITOR.NODE_ELEMENT &&
|
||||
node2.type == node1.type ) {
|
||||
index1 = CKEDITOR.tools.indexOf( tableOrder, node1.name );
|
||||
index2 = CKEDITOR.tools.indexOf( tableOrder, node2.name );
|
||||
}
|
||||
|
||||
// Make sure the sort is stable, if no order can be established above.
|
||||
if ( !( index1 > -1 && index2 > -1 && index1 != index2 ) ) {
|
||||
index1 = getNodeIndex( node1 );
|
||||
index2 = getNodeIndex( node2 );
|
||||
}
|
||||
|
||||
return index1 > index2 ? 1 : -1;
|
||||
});
|
||||
},
|
||||
|
||||
embed: function( element ) {
|
||||
var parent = element.parent;
|
||||
|
||||
// If the <embed> is child of a <object>, copy the width
|
||||
// and height attributes from it.
|
||||
if ( parent && parent.name == 'object' ) {
|
||||
var parentWidth = parent.attributes.width,
|
||||
parentHeight = parent.attributes.height;
|
||||
parentWidth && ( element.attributes.width = parentWidth );
|
||||
parentHeight && ( element.attributes.height = parentHeight );
|
||||
}
|
||||
},
|
||||
// Restore param elements into self-closing.
|
||||
param: function( param ) {
|
||||
param.children = [];
|
||||
param.isEmpty = true;
|
||||
return param;
|
||||
},
|
||||
|
||||
// Remove empty link but not empty anchor.(#3829)
|
||||
a: function( element ) {
|
||||
if ( !( element.children.length || element.attributes.name || element.attributes[ 'data-cke-saved-name' ] ) ) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Remove dummy span in webkit.
|
||||
span: function( element ) {
|
||||
if ( element.attributes[ 'class' ] == 'Apple-style-span' )
|
||||
delete element.name;
|
||||
},
|
||||
|
||||
html: function( element ) {
|
||||
delete element.attributes.contenteditable;
|
||||
delete element.attributes[ 'class' ];
|
||||
},
|
||||
|
||||
body: function( element ) {
|
||||
delete element.attributes.spellcheck;
|
||||
delete element.attributes.contenteditable;
|
||||
},
|
||||
|
||||
style: function( element ) {
|
||||
var child = element.children[ 0 ];
|
||||
child && child.value && ( child.value = CKEDITOR.tools.trim( child.value ) );
|
||||
|
||||
if ( !element.attributes.type )
|
||||
element.attributes.type = 'text/css';
|
||||
},
|
||||
|
||||
title: function( element ) {
|
||||
var titleText = element.children[ 0 ];
|
||||
|
||||
// Append text-node to title tag if not present (i.e. non-IEs) (#9882).
|
||||
!titleText && append( element, titleText = new CKEDITOR.htmlParser.text() );
|
||||
|
||||
// Transfer data-saved title to title tag.
|
||||
titleText.value = element.attributes[ 'data-cke-title' ] || '';
|
||||
}
|
||||
},
|
||||
|
||||
attributes: {
|
||||
'class': function( value, element ) {
|
||||
// Remove all class names starting with "cke_".
|
||||
return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ( CKEDITOR.env.ie ) {
|
||||
// IE outputs style attribute in capital letters. We should convert
|
||||
// them back to lower case, while not hurting the values (#5930)
|
||||
defaultHtmlFilterRules.attributes.style = function( value, element ) {
|
||||
return value.replace( /(^|;)([^\:]+)/g, function( match ) {
|
||||
return match.toLowerCase();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function protectReadOnly( element ) {
|
||||
var attrs = element.attributes;
|
||||
|
||||
// We should flag that the element was locked by our code so
|
||||
// it'll be editable by the editor functions (#6046).
|
||||
if ( attrs.contenteditable != "false" )
|
||||
attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1;
|
||||
|
||||
attrs.contenteditable = "false";
|
||||
}
|
||||
|
||||
function unprotectReadyOnly( element ) {
|
||||
var attrs = element.attributes;
|
||||
switch ( attrs[ 'data-cke-editable' ] ) {
|
||||
case 'true':
|
||||
attrs.contenteditable = 'true';
|
||||
break;
|
||||
case '1':
|
||||
delete attrs.contenteditable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Disable form elements editing mode provided by some browsers. (#5746)
|
||||
for ( var i in { input:1,textarea:1 } ) {
|
||||
defaultDataFilterRules.elements[ i ] = protectReadOnly;
|
||||
defaultHtmlFilterRules.elements[ i ] = unprotectReadyOnly;
|
||||
}
|
||||
|
||||
var protectElementRegex = /<(a|area|img|input|source)\b([^>]*)>/gi,
|
||||
protectAttributeRegex = /\s(on\w+|href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi;
|
||||
|
||||
// Note: we use lazy star '*?' to prevent eating everything up to the last occurrence of </style> or </textarea>.
|
||||
var protectElementsRegex = /(?:<style(?=[ >])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,
|
||||
protectTextareaRegex = /(<textarea(?=[ >])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,
|
||||
encodedElementsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
|
||||
|
||||
var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
|
||||
unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi;
|
||||
|
||||
var protectSelfClosingRegex = /<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi;
|
||||
|
||||
function protectAttributes( html ) {
|
||||
return html.replace( protectElementRegex, function( element, tag, attributes ) {
|
||||
return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) {
|
||||
// Avoid corrupting the inline event attributes (#7243).
|
||||
// We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218)
|
||||
if ( !( /^on/ ).test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 ) {
|
||||
fullAttr = fullAttr.slice( 1 ); // Strip the space.
|
||||
return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr;
|
||||
}
|
||||
|
||||
return fullAttr;
|
||||
}) + '>';
|
||||
});
|
||||
}
|
||||
|
||||
function protectElements( html, regex ) {
|
||||
return html.replace( regex, function( match, tag, content ) {
|
||||
// Encode < and > in textarea because this won't be done by a browser, since
|
||||
// textarea will be protected during passing data through fix bin.
|
||||
if ( match.indexOf( '<textarea' ) === 0 )
|
||||
match = tag + unprotectRealComments( content ).replace( /</g, '<' ).replace( />/g, '>' ) + '</textarea>';
|
||||
|
||||
return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
|
||||
});
|
||||
}
|
||||
|
||||
function unprotectElements( html ) {
|
||||
return html.replace( encodedElementsRegex, function( match, encoded ) {
|
||||
return decodeURIComponent( encoded );
|
||||
});
|
||||
}
|
||||
|
||||
function protectElementsNames( html ) {
|
||||
return html.replace( protectElementNamesRegex, '$1cke:$2' );
|
||||
}
|
||||
|
||||
function unprotectElementNames( html ) {
|
||||
return html.replace( unprotectElementNamesRegex, '$1$2' );
|
||||
}
|
||||
|
||||
function protectSelfClosingElements( html ) {
|
||||
return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' );
|
||||
}
|
||||
|
||||
function protectPreFormatted( html ) {
|
||||
return CKEDITOR.env.opera ? html : html.replace( /(<pre\b[^>]*>)(\r\n|\n)/g, '$1$2$2' );
|
||||
}
|
||||
|
||||
function protectRealComments( html ) {
|
||||
return html.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match ) {
|
||||
return '<!--' + protectedSourceMarker +
|
||||
'{C}' +
|
||||
encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) +
|
||||
'-->';
|
||||
});
|
||||
}
|
||||
|
||||
function unprotectRealComments( html ) {
|
||||
return html.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match, data ) {
|
||||
return decodeURIComponent( data );
|
||||
});
|
||||
}
|
||||
|
||||
function unprotectSource( html, editor ) {
|
||||
var store = editor._.dataStore;
|
||||
|
||||
return html.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match, data ) {
|
||||
return decodeURIComponent( data );
|
||||
}).replace( /\{cke_protected_(\d+)\}/g, function( match, id ) {
|
||||
return store && store[ id ] || '';
|
||||
});
|
||||
}
|
||||
|
||||
function protectSource( data, editor ) {
|
||||
var protectedHtml = [],
|
||||
protectRegexes = editor.config.protectedSource,
|
||||
store = editor._.dataStore || ( editor._.dataStore = { id:1 } ),
|
||||
tempRegex = /<\!--\{cke_temp(comment)?\}(\d*?)-->/g;
|
||||
|
||||
var regexes = [
|
||||
// Script tags will also be forced to be protected, otherwise
|
||||
// IE will execute them.
|
||||
( /<script[\s\S]*?<\/script>/gi ),
|
||||
|
||||
// <noscript> tags (get lost in IE and messed up in FF).
|
||||
/<noscript[\s\S]*?<\/noscript>/gi
|
||||
].concat( protectRegexes );
|
||||
|
||||
// First of any other protection, we must protect all comments
|
||||
// to avoid loosing them (of course, IE related).
|
||||
// Note that we use a different tag for comments, as we need to
|
||||
// transform them when applying filters.
|
||||
data = data.replace( ( /<!--[\s\S]*?-->/g ), function( match ) {
|
||||
return '<!--{cke_tempcomment}' + ( protectedHtml.push( match ) - 1 ) + '-->';
|
||||
});
|
||||
|
||||
for ( var i = 0; i < regexes.length; i++ ) {
|
||||
data = data.replace( regexes[ i ], function( match ) {
|
||||
match = match.replace( tempRegex, // There could be protected source inside another one. (#3869).
|
||||
function( $, isComment, id ) {
|
||||
return protectedHtml[ id ];
|
||||
});
|
||||
|
||||
// Avoid protecting over protected, e.g. /\{.*?\}/
|
||||
return ( /cke_temp(comment)?/ ).test( match ) ? match : '<!--{cke_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->';
|
||||
});
|
||||
}
|
||||
data = data.replace( tempRegex, function( $, isComment, id ) {
|
||||
return '<!--' + protectedSourceMarker +
|
||||
( isComment ? '{C}' : '' ) +
|
||||
encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) +
|
||||
'-->';
|
||||
});
|
||||
|
||||
// Different protection pattern is used for those that
|
||||
// live in attributes to avoid from being HTML encoded.
|
||||
return data.replace( /(['"]).*?\1/g, function( match ) {
|
||||
return match.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match, data ) {
|
||||
store[ store.id ] = decodeURIComponent( data );
|
||||
return '{cke_protected_' + ( store.id++ ) + '}';
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Whether a filler text (non-breaking space entity — ` `) will be
|
||||
* inserted into empty block elements in HTML output.
|
||||
* This is used to render block elements properly with `line-height`.
|
||||
* When a function is specified instead, it will be passed a {@link CKEDITOR.htmlParser.element}
|
||||
* to decide whether adding the filler text by expecting a Boolean return value.
|
||||
*
|
||||
* config.fillEmptyBlocks = false; // Prevent filler nodes in all empty blocks.
|
||||
*
|
||||
* // Prevent filler node only in float cleaners.
|
||||
* config.fillEmptyBlocks = function( element ) {
|
||||
* if ( element.attributes[ 'class' ].indexOf( 'clear-both' ) != -1 )
|
||||
* return false;
|
||||
* };
|
||||
*
|
||||
* @since 3.5
|
||||
* @cfg {Boolean} [fillEmptyBlocks=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
/**
|
||||
* This event is fired by the {@link CKEDITOR.htmlDataProcessor} when input HTML
|
||||
* is to be purified by the {@link CKEDITOR.htmlDataProcessor#toHtml} method.
|
||||
*
|
||||
* By adding listeners with different priorities it is possible
|
||||
* to process input HTML on different stages:
|
||||
*
|
||||
* * 1-4: Data is available in the original string format.
|
||||
* * 5: Data is initially filtered with regexp patterns and parsed to
|
||||
* {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}.
|
||||
* * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#dataFilter}
|
||||
* is not applied yet.
|
||||
* * 10: Data is filtered with {@link CKEDITOR.htmlDataProcessor#dataFilter}.
|
||||
* * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#dataFilter}
|
||||
* has already been applied.
|
||||
* * 15: Data is written back to an HTML string.
|
||||
* * 15-*: Data is available in an HTML string.
|
||||
*
|
||||
* @since 4.1
|
||||
* @event toHtml
|
||||
* @member CKEDITOR.editor
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
* @param data
|
||||
* @param {String/CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} data.dataValue Input data to be purified.
|
||||
* @param {String} data.context See {@link CKEDITOR.htmlDataProcessor#toHtml} The `context` argument.
|
||||
* @param {Boolean} data.fixForBody See {@link CKEDITOR.htmlDataProcessor#toHtml} The `fixForBody` argument.
|
||||
* @param {Boolean} data.dontFilter See {@link CKEDITOR.htmlDataProcessor#toHtml} The `dontFilter` argument.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This event is fired when {@link CKEDITOR.htmlDataProcessor} is converting
|
||||
* internal HTML to output data HTML.
|
||||
*
|
||||
* See {@link #toHtml} event documentation for more details.
|
||||
*
|
||||
* @since 4.1
|
||||
* @event toDataFormat
|
||||
* @member CKEDITOR.editor
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
* @param data
|
||||
* @param {String/CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} data.dataValue Output data to be prepared.
|
||||
*/
|
||||
|
||||
207
lib/ckeditor4/core/htmlparser.js
Executable file
207
lib/ckeditor4/core/htmlparser.js
Executable file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides an "event like" system to parse strings of HTML data.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* parser.onTagOpen = function( tagName, attributes, selfClosing ) {
|
||||
* alert( tagName );
|
||||
* };
|
||||
* parser.parse( '<p>Some <b>text</b>.</p>' ); // Alerts 'p', 'b'.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a htmlParser class instance.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* parser.onTagOpen = function( tagName, attributes, selfClosing ) {
|
||||
* alert( tagName ); // e.g. 'b'
|
||||
* } );
|
||||
* parser.parse( '<!-- Example --><b>Hello</b>' );
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
onTagOpen: function() {},
|
||||
|
||||
/**
|
||||
* Function to be fired when a tag closer is found. This function
|
||||
* should be overriden when using this class.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* parser.onTagClose = function( tagName ) {
|
||||
* alert( tagName ); // 'b'
|
||||
* } );
|
||||
* parser.parse( '<!-- Example --><b>Hello</b>' );
|
||||
*
|
||||
* @param {String} tagName The tag name. The name is guarantted to be lowercased.
|
||||
*/
|
||||
onTagClose: function() {},
|
||||
|
||||
/**
|
||||
* Function to be fired when text is found. This function
|
||||
* should be overriden when using this class.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* parser.onText = function( text ) {
|
||||
* alert( text ); // 'Hello'
|
||||
* } );
|
||||
* parser.parse( '<!-- Example --><b>Hello</b>' );
|
||||
*
|
||||
* @param {String} text The text found.
|
||||
*/
|
||||
onText: function() {},
|
||||
|
||||
/**
|
||||
* Function to be fired when CDATA section is found. This function
|
||||
* should be overriden when using this class.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* parser.onCDATA = function( cdata ) {
|
||||
* alert( cdata ); // 'var hello;'
|
||||
* } );
|
||||
* parser.parse( '<script>var hello;</script>' );
|
||||
*
|
||||
* @param {String} cdata The CDATA been found.
|
||||
*/
|
||||
onCDATA: function() {},
|
||||
|
||||
/**
|
||||
* Function to be fired when a commend is found. This function
|
||||
* should be overriden when using this class.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* parser.onComment = function( comment ) {
|
||||
* alert( comment ); // ' Example '
|
||||
* } );
|
||||
* parser.parse( '<!-- Example --><b>Hello</b>' );
|
||||
*
|
||||
* @param {String} comment The comment text.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* var parser = new CKEDITOR.htmlParser();
|
||||
* // The onTagOpen, onTagClose, onText and onComment should be overriden
|
||||
* // at this point.
|
||||
* parser.parse( '<!-- Example --><b>Hello</b>' );
|
||||
*
|
||||
* @param {String} html The HTML to be parsed.
|
||||
*/
|
||||
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 ) );
|
||||
}
|
||||
};
|
||||
})();
|
||||
152
lib/ckeditor4/core/htmlparser/basicwriter.js
Executable file
152
lib/ckeditor4/core/htmlparser/basicwriter.js
Executable file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @class
|
||||
* @todo
|
||||
*/
|
||||
CKEDITOR.htmlParser.basicWriter = CKEDITOR.tools.createClass({
|
||||
/**
|
||||
* Creates a basicWriter class instance.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
$: function() {
|
||||
this._ = {
|
||||
output: []
|
||||
};
|
||||
},
|
||||
|
||||
proto: {
|
||||
/**
|
||||
* Writes the tag opening part for a opener tag.
|
||||
*
|
||||
* // Writes '<p'.
|
||||
* writer.openTag( 'p', { class : 'MyClass', id : 'MyId' } );
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
openTag: function( tagName, attributes ) {
|
||||
this._.output.push( '<', tagName );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the tag closing part for a opener tag.
|
||||
*
|
||||
* // Writes '>'.
|
||||
* writer.openTagClose( 'p', false );
|
||||
*
|
||||
* // Writes ' />'.
|
||||
* writer.openTagClose( 'br', true );
|
||||
*
|
||||
* @param {String} tagName The element name for this tag.
|
||||
* @param {Boolean} isSelfClose Indicates that this is a self-closing tag,
|
||||
* like `<br>` or `<img>`.
|
||||
*/
|
||||
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}.
|
||||
*
|
||||
* // Writes ' class="MyClass"'.
|
||||
* writer.attribute( 'class', 'MyClass' );
|
||||
*
|
||||
* @param {String} attName The attribute name.
|
||||
* @param {String} attValue The attribute value.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* // Writes '</p>'.
|
||||
* writer.closeTag( 'p' );
|
||||
*
|
||||
* @param {String} tagName The element name for this tag.
|
||||
*/
|
||||
closeTag: function( tagName ) {
|
||||
this._.output.push( '</', tagName, '>' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes text.
|
||||
*
|
||||
* // Writes 'Hello Word'.
|
||||
* writer.text( 'Hello Word' );
|
||||
*
|
||||
* @param {String} text The text value.
|
||||
*/
|
||||
text: function( text ) {
|
||||
this._.output.push( text );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes a comment.
|
||||
*
|
||||
* // Writes '<!-- My comment -->'.
|
||||
* writer.comment( ' My comment ' );
|
||||
*
|
||||
* @param {String} comment The comment text.
|
||||
*/
|
||||
comment: function( comment ) {
|
||||
this._.output.push( '<!--', comment, '-->' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes any kind of data to the ouput.
|
||||
*
|
||||
* writer.write( 'This is an <b>example</b>.' );
|
||||
*
|
||||
* @param {String} data
|
||||
*/
|
||||
write: function( data ) {
|
||||
this._.output.push( data );
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties the current output buffer.
|
||||
*
|
||||
* writer.reset();
|
||||
*/
|
||||
reset: function() {
|
||||
this._.output = [];
|
||||
this._.indent = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties the current output buffer.
|
||||
*
|
||||
* var html = writer.getHtml();
|
||||
*
|
||||
* @param {Boolean} reset Indicates that the {@link #reset} method is to
|
||||
* be automatically called after retrieving the HTML.
|
||||
* @returns {String} The HTML written to the writer so far.
|
||||
*/
|
||||
getHtml: function( reset ) {
|
||||
var html = this._.output.join( '' );
|
||||
|
||||
if ( reset )
|
||||
this.reset();
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
});
|
||||
48
lib/ckeditor4/core/htmlparser/cdata.js
Executable file
48
lib/ckeditor4/core/htmlparser/cdata.js
Executable file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* A lightweight representation of HTML CDATA.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates a cdata class instance.
|
||||
* @param {String} value The CDATA section value.
|
||||
*/
|
||||
CKEDITOR.htmlParser.cdata = function( value ) {
|
||||
/**
|
||||
* The CDATA value.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.cdata.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* CDATA has the same type as {@link CKEDITOR.htmlParser.text} This is
|
||||
* a constant value set to {@link CKEDITOR#NODE_TEXT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_TEXT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_TEXT,
|
||||
|
||||
filter: function() {},
|
||||
|
||||
/**
|
||||
* Writes the CDATA with no special manipulations.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
*/
|
||||
writeHtml: function( writer ) {
|
||||
writer.write( this.value );
|
||||
}
|
||||
} );
|
||||
})();
|
||||
80
lib/ckeditor4/core/htmlparser/comment.js
Executable file
80
lib/ckeditor4/core/htmlparser/comment.js
Executable file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A lightweight representation of an HTML comment.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates a comment class instance.
|
||||
* @param {String} value The comment text value.
|
||||
*/
|
||||
CKEDITOR.htmlParser.comment = function( value ) {
|
||||
/**
|
||||
* The comment text.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: false
|
||||
};
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.comment.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_COMMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_COMMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_COMMENT,
|
||||
|
||||
/**
|
||||
* Filter this comment with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @returns {Boolean} Method returns `false` when this comment has
|
||||
* been removed or replaced with other node. This is an information for
|
||||
* {@link CKEDITOR.htmlParser.element#filterChildren} that it has
|
||||
* to repeat filter on current position in parent's children array.
|
||||
*/
|
||||
filter: function( filter ) {
|
||||
var comment = this.value;
|
||||
|
||||
if ( !( comment = filter.onComment( comment, this ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( typeof comment != 'string' ) {
|
||||
this.replaceWith( comment );
|
||||
return false;
|
||||
}
|
||||
|
||||
this.value = comment;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the HTML representation of this comment to a CKEDITOR.htmlWriter.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
|
||||
* **Note:** it's unsafe to filter offline (not appended) node.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
writer.comment( this.value );
|
||||
}
|
||||
} );
|
||||
348
lib/ckeditor4/core/htmlparser/element.js
Executable file
348
lib/ckeditor4/core/htmlparser/element.js
Executable file
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A lightweight representation of an HTML element.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates an element class instance.
|
||||
* @param {String} name The element name.
|
||||
* @param {Object} attributes And object holding all attributes defined for
|
||||
* this element.
|
||||
*/
|
||||
CKEDITOR.htmlParser.element = function( name, attributes ) {
|
||||
/**
|
||||
* The element name.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* Holds the attributes defined for this element.
|
||||
*
|
||||
* @property {Object}
|
||||
*/
|
||||
this.attributes = attributes || {};
|
||||
|
||||
/**
|
||||
* The nodes that are direct children of this element.
|
||||
*/
|
||||
this.children = [];
|
||||
|
||||
// Reveal the real semantic of our internal custom tag name (#6639),
|
||||
// when resolving whether it's block like.
|
||||
var realName = name || '',
|
||||
prefixed = realName.match( /^cke:(.*)/ );
|
||||
prefixed && ( realName = prefixed[ 1 ] );
|
||||
|
||||
var isBlockLike = !!( CKEDITOR.dtd.$nonBodyContent[ realName ] || CKEDITOR.dtd.$block[ realName ] || CKEDITOR.dtd.$listItem[ realName ] || CKEDITOR.dtd.$tableContent[ realName ] || CKEDITOR.dtd.$nonEditable[ realName ] || realName == 'br' );
|
||||
|
||||
this.isEmpty = !!CKEDITOR.dtd.$empty[ name ];
|
||||
this.isUnknown = !CKEDITOR.dtd[ name ];
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: isBlockLike,
|
||||
hasInlineStarted: this.isEmpty || !isBlockLike
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Object presentation of CSS style declaration text.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a cssStyle class instance.
|
||||
* @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"'
|
||||
// TODO reuse CSS methods from tools.
|
||||
( 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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Serialize CSS style declaration to string.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
toString: function() {
|
||||
var output = [];
|
||||
for ( var i in rules )
|
||||
rules[ i ] && output.push( i, ':', rules[ i ], ';' );
|
||||
return output.join( '' );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/** @class CKEDITOR.htmlParser.element */
|
||||
(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;
|
||||
},
|
||||
fragProto = CKEDITOR.htmlParser.fragment.prototype;
|
||||
|
||||
CKEDITOR.htmlParser.element.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_ELEMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_ELEMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_ELEMENT,
|
||||
|
||||
/**
|
||||
* Adds a node to the element children list.
|
||||
*
|
||||
* @method
|
||||
* @param {CKEDITOR.htmlParser.node} node The node to be added.
|
||||
* @param {Number} [index] From where the insertion happens.
|
||||
*/
|
||||
add: fragProto.add,
|
||||
|
||||
/**
|
||||
* Clone this element.
|
||||
*
|
||||
* @returns {CKEDITOR.htmlParser.element} The element clone.
|
||||
*/
|
||||
clone: function() {
|
||||
return new CKEDITOR.htmlParser.element( this.name, this.attributes );
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter this element and its children with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @returns {Boolean} Method returns `false` when this element has
|
||||
* been removed or replaced with other. This is an information for
|
||||
* {@link #filterChildren} that it has to repeat filter on current
|
||||
* position in parent's children array.
|
||||
*/
|
||||
filter: function( filter ) {
|
||||
var element = this,
|
||||
originalName, name;
|
||||
|
||||
// Filtering if it's the root node.
|
||||
if ( !element.parent )
|
||||
filter.onRoot( element );
|
||||
|
||||
while ( true ) {
|
||||
originalName = element.name;
|
||||
|
||||
if ( !( name = filter.onElementName( originalName ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
element.name = name;
|
||||
|
||||
if ( !( element = filter.onElement( element ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
// New element has been returned - replace current one
|
||||
// and process it (stop processing this and return false, what
|
||||
// means that element has been removed).
|
||||
if ( element !== this ) {
|
||||
this.replaceWith( element );
|
||||
return false;
|
||||
}
|
||||
|
||||
// If name has been changed - continue loop, so in next iteration
|
||||
// filters for new name will be applied to this element.
|
||||
// If name hasn't been changed - stop.
|
||||
if ( element.name == originalName )
|
||||
break;
|
||||
|
||||
// If element has been replaced with something of a
|
||||
// different type, then make the replacement filter itself.
|
||||
if ( element.type != CKEDITOR.NODE_ELEMENT ) {
|
||||
this.replaceWith( element );
|
||||
return false;
|
||||
}
|
||||
|
||||
// This indicate that the element has been dropped by
|
||||
// filter but not the children.
|
||||
if ( !element.name ) {
|
||||
this.replaceWithChildren();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var attributes = element.attributes,
|
||||
a, value, newAttrName;
|
||||
|
||||
for ( a in attributes ) {
|
||||
newAttrName = a;
|
||||
value = attributes[ a ];
|
||||
|
||||
// Loop until name isn't modified.
|
||||
// A little bit senseless, but IE would do that anyway
|
||||
// because it iterates with for-in loop even over properties
|
||||
// created during its run.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !element.isEmpty )
|
||||
this.filterChildren( filter );
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter this element's children with given filter.
|
||||
*
|
||||
* Element's children may only be filtered once by one
|
||||
* instance of filter.
|
||||
*
|
||||
* @method filterChildren
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
*/
|
||||
filterChildren: fragProto.filterChildren,
|
||||
|
||||
/**
|
||||
* Writes the element HTML to a CKEDITOR.htmlWriter.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
|
||||
* **Note:** it's unsafe to filter offline (not appended) node.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
var name = this.name,
|
||||
attribsArray = [],
|
||||
attributes = this.attributes,
|
||||
attrName,
|
||||
attr, i, l;
|
||||
|
||||
// Open element tag.
|
||||
writer.openTag( name, attributes );
|
||||
|
||||
// Copy all attributes to an array.
|
||||
for ( attrName in attributes )
|
||||
attribsArray.push( [ attrName, attributes[ attrName ] ] );
|
||||
|
||||
// Sort the attributes by name.
|
||||
if ( writer.sortAttributes )
|
||||
attribsArray.sort( sortAttribs );
|
||||
|
||||
// Send the attributes.
|
||||
for ( i = 0, l = attribsArray.length; i < l; i++ ) {
|
||||
attr = attribsArray[ i ];
|
||||
writer.attribute( attr[ 0 ], attr[ 1 ] );
|
||||
}
|
||||
|
||||
// Close the tag.
|
||||
writer.openTagClose( name, this.isEmpty );
|
||||
|
||||
this.writeChildrenHtml( writer );
|
||||
|
||||
// Close the element.
|
||||
if ( !this.isEmpty )
|
||||
writer.closeTag( name );
|
||||
},
|
||||
|
||||
/**
|
||||
* Send children of this element to the writer.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter]
|
||||
*/
|
||||
writeChildrenHtml: fragProto.writeChildrenHtml,
|
||||
|
||||
/**
|
||||
* Replace this element with its children.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
replaceWithChildren: function() {
|
||||
var children = this.children;
|
||||
|
||||
for ( var i = children.length; i; )
|
||||
children[ --i ].insertAfter( this );
|
||||
|
||||
this.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute callback on each node (of given type) in this element.
|
||||
*
|
||||
* // Create <p> element with foo<b>bar</b>bom as its content.
|
||||
* var elP = CKEDITOR.htmlParser.fragment.fromHtml( 'foo<b>bar</b>bom', 'p' );
|
||||
* elP.forEach( function( node ) {
|
||||
* console.log( node );
|
||||
* } );
|
||||
* // Will log:
|
||||
* // 1. document fragment,
|
||||
* // 2. <p> element,
|
||||
* // 3. "foo" text node,
|
||||
* // 4. <b> element,
|
||||
* // 5. "bar" text node,
|
||||
* // 6. "bom" text node.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {Function} callback Function to be executed on every node.
|
||||
* @param {CKEDITOR.htmlParser.node} callback.node Node passed as argument.
|
||||
* @param {Number} [type] If specified `callback` will be executed only on nodes of this type.
|
||||
* @param {Boolean} [skipRoot] Don't execute `callback` on this element.
|
||||
*/
|
||||
forEach: fragProto.forEach
|
||||
} );
|
||||
})();
|
||||
275
lib/ckeditor4/core/htmlparser/filter.js
Executable file
275
lib/ckeditor4/core/htmlparser/filter.js
Executable file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @class
|
||||
* @todo we need examples...
|
||||
*/
|
||||
CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass({
|
||||
/**
|
||||
* @constructor Creates a filter class instance.
|
||||
* @todo param
|
||||
*/
|
||||
$: function( rules ) {
|
||||
/**
|
||||
* ID of filter instance, which is used to mark elements
|
||||
* to which this filter has been already applied.
|
||||
*
|
||||
* @property {Number} id
|
||||
* @readonly
|
||||
*/
|
||||
this.id = CKEDITOR.tools.getNextNumber();
|
||||
|
||||
this._ = {
|
||||
elementNames: [],
|
||||
attributeNames: [],
|
||||
elements: { $length: 0 },
|
||||
attributes: { $length: 0 }
|
||||
};
|
||||
|
||||
if ( rules )
|
||||
this.addRules( rules, 10 );
|
||||
},
|
||||
|
||||
proto: {
|
||||
/**
|
||||
* Add rules to this filter
|
||||
*
|
||||
* @param rules Object containing filter rules.
|
||||
* @param {Number} [priority=10]
|
||||
*/
|
||||
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 node rules.
|
||||
this._.root = transformNamedItem( this._.root, rules.root, priority ) || this._.root;
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply this filter to given node.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.node} node The node to be filtered.
|
||||
*/
|
||||
applyTo: function( node ) {
|
||||
node.filter( this );
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
onRoot: 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();
|
||||
}
|
||||
};
|
||||
*/
|
||||
613
lib/ckeditor4/core/htmlparser/fragment.js
Executable file
613
lib/ckeditor4/core/htmlparser/fragment.js
Executable file
@@ -0,0 +1,613 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A lightweight representation of an HTML DOM structure.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a fragment class instance.
|
||||
*/
|
||||
CKEDITOR.htmlParser.fragment = function() {
|
||||
/**
|
||||
* The nodes contained in the root of this fragment.
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
|
||||
* alert( fragment.children.length ); // 2
|
||||
*/
|
||||
this.children = [];
|
||||
|
||||
/**
|
||||
* Get the fragment parent. Should always be null.
|
||||
*
|
||||
* @property {Object} [=null]
|
||||
*/
|
||||
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 );
|
||||
|
||||
var listBlocks = { ol:1,ul:1 };
|
||||
|
||||
// Dtd of the fragment element, basically it accept anything except for intermediate structure, e.g. orphan <li>.
|
||||
var rootDtd = CKEDITOR.tools.extend( {}, { html:1 }, CKEDITOR.dtd.html, CKEDITOR.dtd.body, CKEDITOR.dtd.head, { style:1,script:1 } );
|
||||
|
||||
function isRemoveEmpty( node ) {
|
||||
// Empty link is to be removed when empty but not anchor. (#7894)
|
||||
return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
|
||||
* alert( fragment.children[ 0 ].name ); // 'b'
|
||||
* alert( fragment.children[ 1 ].value ); // ' Text'
|
||||
*
|
||||
* @static
|
||||
* @param {String} fragmentHtml The HTML to be parsed, filling the fragment.
|
||||
* @param {CKEDITOR.htmlParser.element/String} [parent] Optional contextual
|
||||
* element which makes the content been parsed as the content of this element and fix
|
||||
* to match it.
|
||||
* If not provided, then {@link CKEDITOR.htmlParser.fragment} will be used
|
||||
* as the parent and it will be returned.
|
||||
* @param {String/Boolean} [fixingBlock] When `parent` is a block limit element,
|
||||
* and the param is a string value other than `false`, it is to
|
||||
* avoid having block-less content as the direct children of parent by wrapping
|
||||
* the content with a block element of the specified tag, e.g.
|
||||
* when `fixingBlock` specified as `p`, the content `<body><i>foo</i></body>`
|
||||
* will be fixed into `<body><p><i>foo</i></p></body>`.
|
||||
* @returns {CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} The created fragment or passed `parent`.
|
||||
*/
|
||||
CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, parent, fixingBlock ) {
|
||||
var parser = new CKEDITOR.htmlParser();
|
||||
|
||||
var root = parent instanceof CKEDITOR.htmlParser.element ? parent : typeof parent == 'string' ? new CKEDITOR.htmlParser.element( parent ) : new CKEDITOR.htmlParser.fragment();
|
||||
|
||||
var pendingInline = [],
|
||||
pendingBRs = [],
|
||||
currentNode = root,
|
||||
// Indicate we're inside a <textarea> element, spaces should be touched differently.
|
||||
inTextarea = root.name == 'textarea',
|
||||
// Indicate we're inside a <pre> element, spaces should be touched differently.
|
||||
inPre = root.name == 'pre';
|
||||
|
||||
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--;
|
||||
} else {
|
||||
// Some element of the same type cannot be nested, flat them,
|
||||
// e.g. <a href="#">foo<a href="#">bar</a></a>. (#7894)
|
||||
if ( pendingName == currentNode.name )
|
||||
addElement( currentNode, currentNode.parent, 1 ), i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendPendingBRs() {
|
||||
while ( pendingBRs.length )
|
||||
addElement( pendingBRs.shift(), currentNode );
|
||||
}
|
||||
|
||||
// Rtrim empty spaces on block end boundary. (#3585)
|
||||
function removeTailWhitespace( element ) {
|
||||
if ( element._.isBlockLike && element.name != 'pre' && element.name != 'textarea' ) {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
target = target || currentNode || root;
|
||||
|
||||
// Current element might be mangled by fix body below,
|
||||
// save it for restore later.
|
||||
var savedCurrent = currentNode;
|
||||
|
||||
// Ignore any element that has already been added.
|
||||
if ( element.previous === undefined ) {
|
||||
if ( checkAutoParagraphing( target, element ) ) {
|
||||
// Create a <p> in the fragment.
|
||||
currentNode = target;
|
||||
parser.onTagOpen( fixingBlock, {} );
|
||||
|
||||
// The new target now is the <p>.
|
||||
element.returnPoint = target = currentNode;
|
||||
}
|
||||
|
||||
removeTailWhitespace( element );
|
||||
|
||||
// Avoid adding empty inline.
|
||||
if ( !( isRemoveEmpty( element ) && !element.children.length ) )
|
||||
target.add( element );
|
||||
|
||||
if ( element.name == 'pre' )
|
||||
inPre = false;
|
||||
|
||||
if ( element.name == 'textarea' )
|
||||
inTextarea = false;
|
||||
}
|
||||
|
||||
if ( element.returnPoint ) {
|
||||
currentNode = element.returnPoint;
|
||||
delete element.returnPoint;
|
||||
} else
|
||||
currentNode = moveCurrent ? target : savedCurrent;
|
||||
}
|
||||
|
||||
// Auto paragraphing should happen when inline content enters the root element.
|
||||
function checkAutoParagraphing( parent, node ) {
|
||||
|
||||
// Check for parent that can contain block.
|
||||
if ( ( parent == root || parent.name == 'body' ) && fixingBlock &&
|
||||
( !parent.name || CKEDITOR.dtd[ parent.name ][ fixingBlock ] ) )
|
||||
{
|
||||
var name, realName;
|
||||
if ( node.attributes && ( realName = node.attributes[ 'data-cke-real-element-type' ] ) )
|
||||
name = realName;
|
||||
else
|
||||
name = node.name;
|
||||
|
||||
// Text node, inline elements are subjected, except for <script>/<style>.
|
||||
return name && name in CKEDITOR.dtd.$inline &&
|
||||
!( name in CKEDITOR.dtd.head ) &&
|
||||
!node.isOrphan ||
|
||||
node.type == CKEDITOR.NODE_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
// Judge whether two element tag names are likely the siblings from the same
|
||||
// structural element.
|
||||
function possiblySibling( tag1, tag2 ) {
|
||||
|
||||
if ( tag1 in CKEDITOR.dtd.$listItem || tag1 in CKEDITOR.dtd.$tableContent )
|
||||
return tag1 == tag2 || tag1 == 'dt' && tag2 == 'dd' || tag1 == 'dd' && tag2 == 'dt';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = optionalClose;
|
||||
|
||||
// This is a tag to be removed if empty, so do not add it immediately.
|
||||
if ( isRemoveEmpty( element ) ) {
|
||||
pendingInline.push( element );
|
||||
return;
|
||||
} else if ( tagName == 'pre' )
|
||||
inPre = true;
|
||||
else if ( tagName == 'br' && inPre ) {
|
||||
currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) );
|
||||
return;
|
||||
} else if ( tagName == 'textarea' )
|
||||
inTextarea = true;
|
||||
|
||||
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, but NOT to create
|
||||
// new list for the following ones, fix them instead. (#6975)
|
||||
// <dl><dt>foo<dd>bar</dl>
|
||||
// <ul><li>foo<li>bar</ul>
|
||||
else if ( tagName in CKEDITOR.dtd.$listItem &&
|
||||
!possiblySibling( tagName, currentName ) ) {
|
||||
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. <td>td1<td>td2</td>), we simply add this new one before it,
|
||||
// and most importantly, return back to here once this element is added,
|
||||
// e.g. <table><tr><td>td1</td><p>p1</p><td>td2</td></tr></table>
|
||||
else if ( currentName in nonBreakingBlocks &&
|
||||
!possiblySibling( tagName, currentName ) ) {
|
||||
!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 != root && 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 != root ) {
|
||||
// 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 ( 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' )
|
||||
fixingBlock = false;
|
||||
};
|
||||
|
||||
parser.onText = function( text ) {
|
||||
// Trim empty spaces at beginning of text contents except <pre> and <textarea>.
|
||||
if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre && !inTextarea ) {
|
||||
text = CKEDITOR.tools.ltrim( text );
|
||||
|
||||
if ( text.length === 0 )
|
||||
return;
|
||||
}
|
||||
|
||||
var currentName = currentNode.name,
|
||||
currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd;
|
||||
|
||||
// Fix orphan text in list/table. (#8540) (#8870)
|
||||
if ( !inTextarea && !currentDtd[ '#' ] && currentName in nonBreakingBlocks ) {
|
||||
parser.onTagOpen( currentName in listBlocks ? 'li' : currentName == 'dl' ? 'dd' : currentName == 'table' ? 'tr' : currentName == 'tr' ? 'td' : '' );
|
||||
parser.onText( text );
|
||||
return;
|
||||
}
|
||||
|
||||
sendPendingBRs();
|
||||
checkPending();
|
||||
|
||||
// Shrinking consequential spaces into one single for all elements
|
||||
// text contents.
|
||||
if ( !inPre && !inTextarea )
|
||||
text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );
|
||||
|
||||
text = new CKEDITOR.htmlParser.text( text );
|
||||
|
||||
|
||||
if ( checkAutoParagraphing( currentNode, text ) )
|
||||
this.onTagOpen( fixingBlock, {}, 0, 1 );
|
||||
|
||||
currentNode.add( 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 != root )
|
||||
addElement( currentNode, currentNode.parent, 1 );
|
||||
|
||||
removeTailWhitespace( root );
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.fragment.prototype = {
|
||||
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_DOCUMENT_FRAGMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_DOCUMENT_FRAGMENT,
|
||||
|
||||
/**
|
||||
* Adds a node to this fragment.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.node} node The node to be added.
|
||||
* @param {Number} [index] From where the insertion happens.
|
||||
*/
|
||||
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 );
|
||||
|
||||
if ( !this._.hasInlineStarted )
|
||||
this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter this fragment's content with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
*/
|
||||
filter: function( filter ) {
|
||||
// Apply the root filter.
|
||||
filter.onRoot( this );
|
||||
|
||||
this.filterChildren( filter );
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter this fragment's children with given filter.
|
||||
*
|
||||
* Element's children may only be filtered once by one
|
||||
* instance of filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @param {Boolean} [filterRoot] Whether to apply the "root" filter rule specified in the `filter`.
|
||||
*/
|
||||
filterChildren: function( filter, filterRoot ) {
|
||||
// If this element's children were already filtered
|
||||
// by current filter, don't filter them 2nd time.
|
||||
// This situation may occur when filtering bottom-up
|
||||
// (filterChildren() called manually in element's filter),
|
||||
// or in unpredictable edge cases when filter
|
||||
// is manipulating DOM structure.
|
||||
if ( this.childrenFilteredBy == filter.id )
|
||||
return;
|
||||
|
||||
// Filtering root if enforced.
|
||||
if ( filterRoot && !this.parent )
|
||||
filter.onRoot( this );
|
||||
|
||||
this.childrenFilteredBy = filter.id;
|
||||
|
||||
// Don't cache anything, children array may be modified by filter rule.
|
||||
for ( var i = 0; i < this.children.length; i++ ) {
|
||||
// Stay in place if filter returned false, what means
|
||||
// that node has been removed.
|
||||
if ( this.children[ i ].filter( filter ) === false )
|
||||
i--;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the fragment HTML to a {@link CKEDITOR.htmlParser.basicWriter}.
|
||||
*
|
||||
* 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>'
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to use when writing the HTML.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
this.writeChildrenHtml( writer );
|
||||
},
|
||||
|
||||
/**
|
||||
* Write and filtering the child nodes of this fragment.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to use when writing the HTML.
|
||||
* @param {Boolean} [filterRoot] Whether to apply the "root" filter rule specified in the `filter`.
|
||||
*/
|
||||
writeChildrenHtml: function( writer, filter, filterRoot ) {
|
||||
// Filtering root if enforced.
|
||||
if ( filterRoot && !this.parent && filter )
|
||||
filter.onRoot( this );
|
||||
|
||||
if ( filter )
|
||||
this.filterChildren( filter );
|
||||
|
||||
for ( var i = 0, children = this.children, l = children.length; i < l; i++ )
|
||||
children[ i ].writeHtml( writer );
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute callback on each node (of given type) in this document fragment.
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p>foo<b>bar</b>bom</p>' );
|
||||
* fragment.forEach( function( node ) {
|
||||
* console.log( node );
|
||||
* } );
|
||||
* // Will log:
|
||||
* // 1. document fragment,
|
||||
* // 2. <p> element,
|
||||
* // 3. "foo" text node,
|
||||
* // 4. <b> element,
|
||||
* // 5. "bar" text node,
|
||||
* // 6. "bom" text node.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {Function} callback Function to be executed on every node.
|
||||
* @param {CKEDITOR.htmlParser.node} callback.node Node passed as argument.
|
||||
* @param {Number} [type] If specified `callback` will be executed only on nodes of this type.
|
||||
* @param {Boolean} [skipRoot] Don't execute `callback` on this fragment.
|
||||
*/
|
||||
forEach: function( callback, type, skipRoot ) {
|
||||
if ( !skipRoot && ( !type || this.type == type ) )
|
||||
callback( this );
|
||||
|
||||
var children = this.children,
|
||||
node,
|
||||
i = 0,
|
||||
l = children.length;
|
||||
|
||||
for ( ; i < l; i++ ) {
|
||||
node = children[ i ];
|
||||
if ( node.type == CKEDITOR.NODE_ELEMENT )
|
||||
node.forEach( callback, type );
|
||||
else if ( !type || node.type == type )
|
||||
callback( node );
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
98
lib/ckeditor4/core/htmlparser/node.js
Executable file
98
lib/ckeditor4/core/htmlparser/node.js
Executable file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* A lightweight representation of HTML node.
|
||||
*
|
||||
* @since 4.1
|
||||
* @class
|
||||
* @constructor Creates a node class instance.
|
||||
*/
|
||||
CKEDITOR.htmlParser.node = function() {};
|
||||
|
||||
CKEDITOR.htmlParser.node.prototype = {
|
||||
/**
|
||||
* Remove this node from a tree.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
remove: function() {
|
||||
var children = this.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, this ),
|
||||
previous = this.previous,
|
||||
next = this.next;
|
||||
|
||||
previous && ( previous.next = next );
|
||||
next && ( next.previous = previous );
|
||||
children.splice( index, 1 );
|
||||
this.parent = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace this node with given one.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.node} node The node that will replace this one.
|
||||
*/
|
||||
replaceWith: function( node ) {
|
||||
var children = this.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, this ),
|
||||
previous = node.previous = this.previous,
|
||||
next = node.next = this.next;
|
||||
|
||||
previous && ( previous.next = node );
|
||||
next && ( next.previous = node );
|
||||
|
||||
children[ index ] = node;
|
||||
|
||||
node.parent = this.parent;
|
||||
this.parent = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert this node after given one.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.node} node The node that will precede this element.
|
||||
*/
|
||||
insertAfter: function( node ) {
|
||||
var children = node.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, node ),
|
||||
next = node.next;
|
||||
|
||||
children.splice( index + 1, 0, this );
|
||||
|
||||
this.next = node.next;
|
||||
this.previous = node;
|
||||
node.next = this;
|
||||
next && ( next.previous = this );
|
||||
|
||||
this.parent = node.parent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert this node before given one.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.node} node The node that will follow this element.
|
||||
*/
|
||||
insertBefore: function( node ) {
|
||||
var children = node.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, node );
|
||||
|
||||
children.splice( index, 0, this );
|
||||
|
||||
this.next = node;
|
||||
this.previous = node.previous;
|
||||
node.previous && ( node.previous.next = this );
|
||||
node.previous = this;
|
||||
|
||||
this.parent = node.parent;
|
||||
}
|
||||
};
|
||||
})();
|
||||
70
lib/ckeditor4/core/htmlparser/text.js
Executable file
70
lib/ckeditor4/core/htmlparser/text.js
Executable file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* A lightweight representation of HTML text.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates a text class instance.
|
||||
* @param {String} value The text node value.
|
||||
*/
|
||||
CKEDITOR.htmlParser.text = function( value ) {
|
||||
/**
|
||||
* The text value.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: false
|
||||
};
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.text.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_TEXT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_TEXT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_TEXT,
|
||||
|
||||
/**
|
||||
* Filter this text node with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @returns {Boolean} Method returns `false` when this text node has
|
||||
* been removed. This is an information for {@link CKEDITOR.htmlParser.element#filterChildren}
|
||||
* that it has to repeat filter on current position in parent's children array.
|
||||
*/
|
||||
filter: function( filter ) {
|
||||
if ( !( this.value = filter.onText( this.value, this ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the HTML representation of this text to a {CKEDITOR.htmlParser.basicWriter}.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
|
||||
* **Note:** it's unsafe to filter offline (not appended) node.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
writer.text( this.value );
|
||||
}
|
||||
} );
|
||||
})();
|
||||
153
lib/ckeditor4/core/keystrokehandler.js
Executable file
153
lib/ckeditor4/core/keystrokehandler.js
Executable file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controls keystrokes typing in an editor instance.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a keystrokeHandler class instance.
|
||||
* @param {CKEDITOR.editor} editor The editor instance.
|
||||
*/
|
||||
CKEDITOR.keystrokeHandler = function( editor ) {
|
||||
if ( editor.keystrokeHandler )
|
||||
return editor.keystrokeHandler;
|
||||
|
||||
/**
|
||||
* List of keystrokes associated to commands. Each entry points to the
|
||||
* command to be executed.
|
||||
*
|
||||
* Since CKEditor 4 there's no need to modify this property directly during the runtime.
|
||||
* Use {@link CKEDITOR.editor#setKeystroke} instead.
|
||||
*/
|
||||
this.keystrokes = {};
|
||||
|
||||
/**
|
||||
* List of keystrokes that should be blocked if not defined at
|
||||
* {@link #keystrokes}. In this way it is possible to block the default
|
||||
* browser behavior for those keystrokes.
|
||||
*/
|
||||
this.blockedKeystrokes = {};
|
||||
|
||||
this._ = {
|
||||
editor: editor
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
(function() {
|
||||
var cancel;
|
||||
|
||||
var onKeyDown = function( event ) {
|
||||
// The DOM event object is passed by the "data" property.
|
||||
event = event.data;
|
||||
|
||||
var keyCombination = event.getKeystroke();
|
||||
var command = this.keystrokes[ keyCombination ];
|
||||
var editor = this._.editor;
|
||||
|
||||
cancel = ( editor.fire( 'key', { keyCode: keyCombination } ) === false );
|
||||
|
||||
if ( !cancel ) {
|
||||
if ( command ) {
|
||||
var data = { from: 'keystrokeHandler' };
|
||||
cancel = ( editor.execCommand( command, data ) !== false );
|
||||
}
|
||||
|
||||
if ( !cancel )
|
||||
cancel = !!this.blockedKeystrokes[ keyCombination ];
|
||||
}
|
||||
|
||||
if ( cancel )
|
||||
event.preventDefault( true );
|
||||
|
||||
return !cancel;
|
||||
};
|
||||
|
||||
var onKeyPress = function( event ) {
|
||||
if ( cancel ) {
|
||||
cancel = false;
|
||||
event.data.preventDefault( true );
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.keystrokeHandler.prototype = {
|
||||
/**
|
||||
* Attaches this keystroke handle to a DOM object. Keystrokes typed
|
||||
* over this object will get handled by this keystrokeHandler.
|
||||
*
|
||||
* @param {CKEDITOR.dom.domObject} domObject The DOM object to attach to.
|
||||
*/
|
||||
attach: function( domObject ) {
|
||||
// For most browsers, it is enough to listen to the keydown event
|
||||
// only.
|
||||
domObject.on( 'keydown', onKeyDown, this );
|
||||
|
||||
// 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.
|
||||
if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
|
||||
domObject.on( 'keypress', onKeyPress, this );
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* A list associating keystrokes to editor commands. Each element in the list
|
||||
* is an array where the first item is the keystroke, and the second is the
|
||||
* name of the command to be executed.
|
||||
*
|
||||
* This setting should be used to define (as well as to overwrite or remove) keystrokes
|
||||
* set by plugins (like `link` and `basicstyles`). If you want to set a keystroke
|
||||
* for your plugin or during the runtime, use {@link CKEDITOR.editor#setKeystroke} instead.
|
||||
*
|
||||
* Since default keystrokes are set by {@link CKEDITOR.editor#setKeystroke}
|
||||
* method, by default `config.keystrokes` is an empty array.
|
||||
*
|
||||
* See {@link CKEDITOR.editor#setKeystroke} documentation for more details
|
||||
* regarding the start up order.
|
||||
*
|
||||
* // Change default CTRL + L keystroke for 'link' command to CTRL + SHIFT + L.
|
||||
* config.keystrokes = [
|
||||
* ...
|
||||
* [ CKEDITOR.CTRL + CKEDITOR.SHIFT + 76, 'link' ], // CTRL + SHIFT + L
|
||||
* ...
|
||||
* ];
|
||||
*
|
||||
* To reset a particular keystroke, the following approach can be used:
|
||||
*
|
||||
* // Disable default CTRL + L keystroke which executes link command by default.
|
||||
* config.keystrokes = [
|
||||
* ...
|
||||
* [ CKEDITOR.CTRL + 76, null ], // CTRL + L
|
||||
* ...
|
||||
* ];
|
||||
*
|
||||
* To reset all default keystrokes an {@link CKEDITOR#instanceReady} callback should be
|
||||
* used. This is since editor defaults are merged rather than overwritten by
|
||||
* user keystrokes.
|
||||
*
|
||||
* **Note**: This can be potentially harmful for an editor. Avoid this unless you're
|
||||
* aware of the consequences.
|
||||
*
|
||||
* // Reset all default keystrokes.
|
||||
* config.on.instanceReady = function() {
|
||||
* this.keystrokeHandler.keystrokes = [];
|
||||
* };
|
||||
*
|
||||
* @cfg {Array} [keystrokes=[]]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when any keyboard key (or combination) is pressed into the editing area.
|
||||
*
|
||||
* @event key
|
||||
* @member CKEDITOR.editor
|
||||
* @param data
|
||||
* @param {Number} data.keyCode A number representing the key code (or combination).
|
||||
* It is the sum of the current key code and the {@link CKEDITOR#CTRL}, {@link CKEDITOR#SHIFT}
|
||||
* and {@link CKEDITOR#ALT} constants, if those are pressed.
|
||||
* @param {CKEDITOR.editor} editor This editor instance.
|
||||
*/
|
||||
89
lib/ckeditor4/core/lang.js
Executable file
89
lib/ckeditor4/core/lang.js
Executable file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var loadedLangs = {};
|
||||
|
||||
/**
|
||||
* Holds language related functions.
|
||||
*
|
||||
* @class
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.lang = {
|
||||
/**
|
||||
* The list of languages available in the editor core.
|
||||
*
|
||||
* alert( CKEDITOR.lang.en ); // 1
|
||||
*/
|
||||
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,'id':1,'is':1,'it':1,'ja':1,'ka':1,'km':1,'ko':1,'ku':1,'lt':1,'lv':1,'mk':1,'mn':1,'ms':1,'nb':1,'nl':1,'no':1,'pl':1,'pt-br':1,'pt':1,'ro':1,'ru':1,'si':1,'sk':1,'sl':1,'sq':1,'sr-latn':1,'sr':1,'sv':1,'th':1,'tr':1,'ug':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.
|
||||
*/
|
||||
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( '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.
|
||||
*
|
||||
* alert( CKEDITOR.lang.detect( 'en' ) ); // e.g., in a German browser: 'de'
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
detect: function( defaultLanguage, probeLanguage ) {
|
||||
var languages = this.languages;
|
||||
probeLanguage = probeLanguage || navigator.userLanguage || navigator.language || defaultLanguage;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
247
lib/ckeditor4/core/loader.js
Executable file
247
lib/ckeditor4/core/loader.js
Executable file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md 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.
|
||||
*
|
||||
* @class
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.loader = (function() {
|
||||
// Table of script names and their dependencies.
|
||||
var scripts = {
|
||||
'_bootstrap': [ 'config', 'creators/inline', 'creators/themedui', 'editable', 'ckeditor', 'plugins', 'scriptloader', 'style', 'tools', /* The following are entries that we want to force loading at the end to avoid dependence recursion */ 'dom/comment', 'dom/elementpath', 'dom/text', 'dom/rangelist', 'skin' ],
|
||||
'ckeditor': [ 'ckeditor_basic', 'dom', 'dtd', 'dom/document', 'dom/element', 'dom/iterator', 'editor', 'event', 'htmldataprocessor', 'htmlparser', 'htmlparser/element', 'htmlparser/fragment', 'htmlparser/filter', 'htmlparser/basicwriter', 'template', 'tools' ],
|
||||
'ckeditor_base': [],
|
||||
'ckeditor_basic': [ 'editor_basic', 'env', 'event' ],
|
||||
'command': [],
|
||||
'config': [ 'ckeditor_base' ],
|
||||
'dom': [],
|
||||
'dom/comment': [ 'dom/node' ],
|
||||
'dom/document': [ 'dom/node', 'dom/window' ],
|
||||
'dom/documentfragment': [ 'dom/element' ],
|
||||
'dom/element': [ 'dom', 'dom/document', 'dom/domobject', 'dom/node', 'dom/nodelist', 'tools' ],
|
||||
'dom/elementpath': [ 'dom/element' ],
|
||||
'dom/event': [],
|
||||
'dom/iterator': [ 'dom/range' ],
|
||||
'dom/node': [ 'dom/domobject', 'tools' ],
|
||||
'dom/nodelist': [ 'dom/node' ],
|
||||
'dom/domobject': [ 'dom/event' ],
|
||||
'dom/range': [ 'dom/document', 'dom/documentfragment', 'dom/element', 'dom/walker' ],
|
||||
'dom/rangelist': [ 'dom/range' ],
|
||||
'dom/text': [ 'dom/node', 'dom/domobject' ],
|
||||
'dom/walker': [ 'dom/node' ],
|
||||
'dom/window': [ 'dom/domobject' ],
|
||||
'dtd': [ 'tools' ],
|
||||
'editable': [ 'editor', 'tools' ],
|
||||
'editor': [ 'command', 'config', 'editor_basic', 'filter', 'focusmanager', 'keystrokehandler', 'lang', 'plugins', 'tools', 'ui' ],
|
||||
'editor_basic': [ 'event' ],
|
||||
'env': [],
|
||||
'event': [],
|
||||
'filter': [ 'dtd', 'tools' ],
|
||||
'focusmanager': [],
|
||||
'htmldataprocessor': [ 'htmlparser', 'htmlparser/basicwriter', 'htmlparser/fragment', 'htmlparser/filter' ],
|
||||
'htmlparser': [],
|
||||
'htmlparser/comment': [ 'htmlparser', 'htmlparser/node' ],
|
||||
'htmlparser/element': [ 'htmlparser', 'htmlparser/fragment','htmlparser/node' ],
|
||||
'htmlparser/fragment': [ 'htmlparser', 'htmlparser/comment', 'htmlparser/text', 'htmlparser/cdata' ],
|
||||
'htmlparser/text': [ 'htmlparser', 'htmlparser/node' ],
|
||||
'htmlparser/cdata': [ 'htmlparser', 'htmlparser/node' ],
|
||||
'htmlparser/filter': [ 'htmlparser' ],
|
||||
'htmlparser/basicwriter': [ 'htmlparser' ],
|
||||
'htmlparser/node': [ 'htmlparser' ],
|
||||
'keystrokehandler': [ 'event' ],
|
||||
'lang': [],
|
||||
'plugins': [ 'resourcemanager' ],
|
||||
'resourcemanager': [ 'scriptloader', 'tools' ],
|
||||
'scriptloader': [ 'dom/element', 'env' ],
|
||||
'selection': [ 'dom/range', 'dom/walker' ],
|
||||
'skin': [],
|
||||
'style': [ 'selection' ],
|
||||
'template': [],
|
||||
'tools': [ 'env' ],
|
||||
'ui': [],
|
||||
'creators/themedui': [],
|
||||
'creators/inline': []
|
||||
};
|
||||
|
||||
var basePath = (function() {
|
||||
// This is a copy of CKEDITOR.basePath, but requires the script having
|
||||
// "_source/loader.js".
|
||||
if ( CKEDITOR && CKEDITOR.basePath )
|
||||
return CKEDITOR.basePath;
|
||||
|
||||
// Find out the editor directory path, based on its <script> tag.
|
||||
var path = '';
|
||||
var scripts = document.getElementsByTagName( 'script' );
|
||||
|
||||
for ( var i = 0; i < scripts.length; i++ ) {
|
||||
var match = scripts[ i ].src.match( /(^|.*?[\\\/])(?:_source\/)?core\/loader.js(?:\?.*)?$/i );
|
||||
|
||||
if ( match ) {
|
||||
path = match[ 1 ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// In IE (only) the script.src string is the raw valued entered in the
|
||||
// HTML. Other browsers return the full resolved URL instead.
|
||||
if ( path.indexOf( '://' ) == -1 ) {
|
||||
// Absolute path.
|
||||
if ( path.indexOf( '/' ) === 0 )
|
||||
path = location.href.match( /^.*?:\/\/[^\/]*/ )[ 0 ] + path;
|
||||
// Relative path.
|
||||
else
|
||||
path = location.href.match( /^[^\?]*\// )[ 0 ] + path;
|
||||
}
|
||||
|
||||
return path;
|
||||
})();
|
||||
|
||||
var timestamp = ( CKEDITOR && CKEDITOR.timestamp ) || ( new Date() ).valueOf(); // %REMOVE_LINE%
|
||||
/* // %REMOVE_LINE%
|
||||
* The production implementation contains a fixed timestamp // %REMOVE_LINE%
|
||||
* generated by the releaser // %REMOVE_LINE%
|
||||
var timestamp = '%TIMESTAMP%';
|
||||
*/ // %REMOVE_LINE%
|
||||
|
||||
var getUrl = function( resource ) {
|
||||
if ( CKEDITOR && CKEDITOR.getUrl )
|
||||
return CKEDITOR.getUrl( resource );
|
||||
|
||||
return basePath + resource + ( resource.indexOf( '?' ) >= 0 ? '&' : '?' ) + 't=' + timestamp;
|
||||
};
|
||||
|
||||
var pendingLoad = [];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The list of loaded scripts in their loading order.
|
||||
*
|
||||
* // Alert the loaded script names.
|
||||
* alert( CKEDITOR.loader.loadedScripts );
|
||||
*/
|
||||
loadedScripts: [],
|
||||
/**
|
||||
* Table of script names and their dependencies.
|
||||
*
|
||||
* @property {Array}
|
||||
*/
|
||||
scripts: scripts,
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
loadPending: function() {
|
||||
var scriptName = pendingLoad.shift();
|
||||
|
||||
if ( !scriptName )
|
||||
return;
|
||||
|
||||
var scriptSrc = getUrl( 'core/' + scriptName + '.js' );
|
||||
|
||||
var script = document.createElement( 'script' );
|
||||
script.type = 'text/javascript';
|
||||
script.src = scriptSrc;
|
||||
|
||||
function onScriptLoaded() {
|
||||
// Append this script to the list of loaded scripts.
|
||||
CKEDITOR.loader.loadedScripts.push( scriptName );
|
||||
|
||||
// Load the next.
|
||||
CKEDITOR.loader.loadPending();
|
||||
}
|
||||
|
||||
// We must guarantee the execution order of the scripts, so we
|
||||
// need to load them one by one. (#4145)
|
||||
// The following if/else block has been taken from the scriptloader core code.
|
||||
if ( typeof( script.onreadystatechange ) !== "undefined" ) {
|
||||
/** @ignore */
|
||||
script.onreadystatechange = function() {
|
||||
if ( script.readyState == 'loaded' || script.readyState == 'complete' ) {
|
||||
script.onreadystatechange = null;
|
||||
onScriptLoaded();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
/** @ignore */
|
||||
script.onload = function() {
|
||||
// Some browsers, such as Safari, may call the onLoad function
|
||||
// immediately. Which will break the loading sequence. (#3661)
|
||||
setTimeout( function() {
|
||||
onScriptLoaded( scriptName );
|
||||
}, 0 );
|
||||
};
|
||||
}
|
||||
|
||||
document.body.appendChild( script );
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a specific script, including its dependencies. This is not a
|
||||
* synchronous loading, which means that the code to be loaded will
|
||||
* not necessarily be available after this call.
|
||||
*
|
||||
* CKEDITOR.loader.load( 'dom/element' );
|
||||
*
|
||||
* @param {String} scriptName
|
||||
* @param {Boolean} [defer=false]
|
||||
* @todo params
|
||||
*/
|
||||
load: function( scriptName, defer ) {
|
||||
// Check if the script has already been loaded.
|
||||
if ( ( 's:' + scriptName ) in this.loadedScripts )
|
||||
return;
|
||||
|
||||
// Get the script dependencies list.
|
||||
var dependencies = scripts[ scriptName ];
|
||||
if ( !dependencies )
|
||||
throw 'The script name"' + scriptName + '" is not defined.';
|
||||
|
||||
// Mark the script as loaded, even before really loading it, to
|
||||
// avoid cross references recursion.
|
||||
// Prepend script name with 's:' to avoid conflict with Array's methods.
|
||||
this.loadedScripts[ 's:' + scriptName ] = true;
|
||||
|
||||
// Load all dependencies first.
|
||||
for ( var i = 0; i < dependencies.length; i++ )
|
||||
this.load( dependencies[ i ], true );
|
||||
|
||||
var scriptSrc = getUrl( 'core/' + scriptName + '.js' );
|
||||
|
||||
// Append the <script> element to the DOM.
|
||||
// If the page is fully loaded, we can't use document.write
|
||||
// but if the script is run while the body is loading then it's safe to use it
|
||||
// Unfortunately, Firefox <3.6 doesn't support document.readyState, so it won't get this improvement
|
||||
if ( document.body && ( !document.readyState || document.readyState == 'complete' ) ) {
|
||||
pendingLoad.push( scriptName );
|
||||
|
||||
if ( !defer )
|
||||
this.loadPending();
|
||||
} else {
|
||||
// Append this script to the list of loaded scripts.
|
||||
this.loadedScripts.push( scriptName );
|
||||
|
||||
document.write( '<script src="' + scriptSrc + '" type="text/javascript"><\/script>' );
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// Check if any script has been defined for autoload.
|
||||
if ( CKEDITOR._autoLoad ) {
|
||||
CKEDITOR.loader.load( CKEDITOR._autoLoad );
|
||||
delete CKEDITOR._autoLoad;
|
||||
}
|
||||
96
lib/ckeditor4/core/plugindefinition.js
Executable file
96
lib/ckeditor4/core/plugindefinition.js
Executable file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the "virtual" {@link CKEDITOR.pluginDefinition} class which
|
||||
* contains the defintion of a plugin. This file is for documentation
|
||||
* purposes only.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Virtual class which just illustrates the features of plugin objects to be
|
||||
* passed to the {@link CKEDITOR.plugins#add} method.
|
||||
*
|
||||
* This class is not really part of the API, so its constructor should not be called.
|
||||
*
|
||||
* @class CKEDITOR.pluginDefinition
|
||||
* @abstract
|
||||
*/
|
||||
|
||||
/**
|
||||
* A list of plugins that are required by this plugin. Note that this property
|
||||
* does not determine the loading order of the plugins.
|
||||
*
|
||||
* CKEDITOR.plugins.add( 'sample', {
|
||||
* requires: [ 'button', 'selection' ]
|
||||
* } );
|
||||
*
|
||||
* @property {Array} requires
|
||||
*/
|
||||
|
||||
/**
|
||||
* A list of language files available for this plugin. These files are stored inside
|
||||
* the `lang` directory inside the plugin directory, follow the name
|
||||
* pattern of `langCode.js`, and contain the language definition created with
|
||||
* {@link CKEDITOR.plugins#setLang}.
|
||||
*
|
||||
* When the plugin is being loaded, the editor checks this list to see if
|
||||
* a language file of the current editor language ({@link CKEDITOR.editor#langCode})
|
||||
* is available, and if so, loads it. Otherwise, the file represented by the first item
|
||||
* in the list is loaded.
|
||||
*
|
||||
* CKEDITOR.plugins.add( 'sample', {
|
||||
* lang: [ 'en', 'fr' ]
|
||||
* } );
|
||||
*
|
||||
* @property {Array} lang
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function called on initialization of every editor instance created in the
|
||||
* page before the {@link #init} call task. The `beforeInit` function will be called for
|
||||
* all plugins, after that the `init` function is called for all of them. This
|
||||
* feature makes it possible to initialize things that could be used in the
|
||||
* `init` function of other plugins.
|
||||
*
|
||||
* CKEDITOR.plugins.add( 'sample', {
|
||||
* beforeInit: function( editor ) {
|
||||
* alert( 'Editor "' + editor.name + '" is to be initialized!' );
|
||||
* }
|
||||
* } );
|
||||
*
|
||||
* @method beforeInit
|
||||
* @param {CKEDITOR.editor} editor The editor instance being initialized.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function called on initialization of every editor instance created in the page.
|
||||
*
|
||||
* CKEDITOR.plugins.add( 'sample', {
|
||||
* init: function( editor ) {
|
||||
* alert( 'Editor "' + editor.name + '" is being initialized!' );
|
||||
* }
|
||||
* } );
|
||||
*
|
||||
* @method init
|
||||
* @param {CKEDITOR.editor} editor The editor instance being initialized.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Announces the plugin as HiDPI-ready (optimized for high pixel density screens, e.g. *Retina*)
|
||||
* by providing high-resolution icons and images. HiDPI icons must be twice as big
|
||||
* (defaults are `16px x 16px`) and stored under `plugin_name/icons/hidpi/` directory.
|
||||
*
|
||||
* The common place for additional HiDPI images used by the plugin (**but not icons**)
|
||||
* is `plugin_name/images/hidpi/` directory.
|
||||
*
|
||||
* This property is optional and only makes sense if `32px x 32px` icons
|
||||
* and high-resolution images actually exist. If this flag is set `true`, the editor
|
||||
* will automatically detect the HiDPI environment and attempt to load the
|
||||
* high-resolution resources.
|
||||
*
|
||||
* @since 4.2
|
||||
* @property {Boolean} hidpi
|
||||
*/
|
||||
119
lib/ckeditor4/core/plugins.js
Executable file
119
lib/ckeditor4/core/plugins.js
Executable file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.plugins} object, which is used to
|
||||
* manage plugins registration and loading.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Manages plugins registration and loading.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.resourceManager
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.plugins = new CKEDITOR.resourceManager( 'plugins/', 'plugin' );
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.plugins )
|
||||
|
||||
CKEDITOR.plugins.load = CKEDITOR.tools.override( CKEDITOR.plugins.load, function( originalLoad ) {
|
||||
var initialized = {};
|
||||
|
||||
return function( name, callback, scope ) {
|
||||
var allPlugins = {};
|
||||
|
||||
var loadPlugins = function( names ) {
|
||||
originalLoad.call( this, names, function( plugins ) {
|
||||
CKEDITOR.tools.extend( allPlugins, plugins );
|
||||
|
||||
var requiredPlugins = [];
|
||||
for ( var pluginName in plugins ) {
|
||||
var plugin = plugins[ pluginName ],
|
||||
requires = plugin && plugin.requires;
|
||||
|
||||
if ( !initialized[ pluginName ] ) {
|
||||
// Register all icons eventually defined by this plugin.
|
||||
if ( plugin.icons ) {
|
||||
var icons = plugin.icons.split( ',' );
|
||||
for ( var ic = icons.length; ic--; ) {
|
||||
CKEDITOR.skin.addIcon( icons[ ic ],
|
||||
plugin.path +
|
||||
'icons/' +
|
||||
( CKEDITOR.env.hidpi && plugin.hidpi ? 'hidpi/' : '' ) +
|
||||
icons[ ic ] +
|
||||
'.png' );
|
||||
}
|
||||
}
|
||||
initialized[ pluginName ] = 1;
|
||||
}
|
||||
|
||||
if ( requires ) {
|
||||
// Trasnform it into an array, if it's not one.
|
||||
if ( requires.split )
|
||||
requires = requires.split( ',' );
|
||||
|
||||
for ( var i = 0; i < requires.length; i++ ) {
|
||||
if ( !allPlugins[ requires[ i ] ] )
|
||||
requiredPlugins.push( requires[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( requiredPlugins.length )
|
||||
loadPlugins.call( this, requiredPlugins );
|
||||
else {
|
||||
// Call the "onLoad" function for all plugins.
|
||||
for ( pluginName in allPlugins ) {
|
||||
plugin = allPlugins[ pluginName ];
|
||||
if ( plugin.onLoad && !plugin.onLoad._called ) {
|
||||
// Make it possible to return false from plugin::onLoad to disable it.
|
||||
if ( plugin.onLoad() === false )
|
||||
delete allPlugins[ pluginName ];
|
||||
|
||||
plugin.onLoad._called = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Call the callback.
|
||||
if ( callback )
|
||||
callback.call( scope || window, allPlugins );
|
||||
}
|
||||
}, this );
|
||||
|
||||
};
|
||||
|
||||
loadPlugins.call( this, name );
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Loads a specific language file, or auto detect it. A callback is
|
||||
* then called when the file gets loaded.
|
||||
*
|
||||
* CKEDITOR.plugins.setLang( 'myPlugin', 'en', {
|
||||
* title: 'My plugin',
|
||||
* selectOption: 'Please select an option'
|
||||
* } );
|
||||
*
|
||||
* @param {String} pluginName The name of the plugin to which the provided translation
|
||||
* should be attached.
|
||||
* @param {String} languageCode The code of the language translation provided.
|
||||
* @param {Object} languageEntries An object that contains pairs of label and
|
||||
* the respective translation.
|
||||
*/
|
||||
CKEDITOR.plugins.setLang = function( pluginName, languageCode, languageEntries ) {
|
||||
var plugin = this.get( pluginName ),
|
||||
pluginLangEntries = plugin.langEntries || ( plugin.langEntries = {} ),
|
||||
pluginLang = plugin.lang || ( plugin.lang = [] );
|
||||
|
||||
if ( pluginLang.split )
|
||||
pluginLang = pluginLang.split( ',' );
|
||||
|
||||
if ( CKEDITOR.tools.indexOf( pluginLang, languageCode ) == -1 )
|
||||
pluginLang.push( languageCode );
|
||||
|
||||
pluginLangEntries[ languageCode ] = languageEntries;
|
||||
};
|
||||
215
lib/ckeditor4/core/resourcemanager.js
Executable file
215
lib/ckeditor4/core/resourcemanager.js
Executable file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.resourceManager} class, which is
|
||||
* the base for resource managers, like plugins.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for resource managers, like plugins. This class is not
|
||||
* intended to be used out of the CKEditor core code.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a resourceManager class instance.
|
||||
* @param {String} basePath The path for the resources folder.
|
||||
* @param {String} fileName The name used for resource files.
|
||||
*/
|
||||
CKEDITOR.resourceManager = function( basePath, fileName ) {
|
||||
/**
|
||||
* The base directory containing all resources.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.basePath = basePath;
|
||||
|
||||
/**
|
||||
* The name used for resource files.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.fileName = fileName;
|
||||
|
||||
/**
|
||||
* Contains references to all resources that have already been registered
|
||||
* with {@link #add}.
|
||||
*/
|
||||
this.registered = {};
|
||||
|
||||
/**
|
||||
* Contains references to all resources that have already been loaded
|
||||
* with {@link #load}.
|
||||
*/
|
||||
this.loaded = {};
|
||||
|
||||
/**
|
||||
* Contains references to all resources that have already been registered
|
||||
* with {@link #addExternal}.
|
||||
*/
|
||||
this.externals = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this._ = {
|
||||
// List of callbacks waiting for plugins to be loaded.
|
||||
waitingList: {}
|
||||
};
|
||||
};
|
||||
|
||||
CKEDITOR.resourceManager.prototype = {
|
||||
/**
|
||||
* Registers a resource.
|
||||
*
|
||||
* CKEDITOR.plugins.add( 'sample', { ... plugin definition ... } );
|
||||
*
|
||||
* @param {String} name The resource name.
|
||||
* @param {Object} [definition] The resource definition.
|
||||
* @see CKEDITOR.pluginDefinition
|
||||
*/
|
||||
add: function( name, definition ) {
|
||||
if ( this.registered[ name ] )
|
||||
throw '[CKEDITOR.resourceManager.add] The resource name "' + name + '" is already registered.';
|
||||
|
||||
var resource = this.registered[ name ] = definition || {};
|
||||
resource.name = name;
|
||||
resource.path = this.getPath( name );
|
||||
|
||||
CKEDITOR.fire( name + CKEDITOR.tools.capitalize( this.fileName ) + 'Ready', resource );
|
||||
|
||||
return this.get( name );
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the definition of a specific resource.
|
||||
*
|
||||
* var definition = CKEDITOR.plugins.get( 'sample' );
|
||||
*
|
||||
* @param {String} name The resource name.
|
||||
* @returns {Object} The registered object.
|
||||
*/
|
||||
get: function( name ) {
|
||||
return this.registered[ name ] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the folder path for a specific loaded resource.
|
||||
*
|
||||
* alert( CKEDITOR.plugins.getPath( 'sample' ) ); // '<editor path>/plugins/sample/'
|
||||
*
|
||||
* @param {String} name The resource name.
|
||||
* @returns {String}
|
||||
*/
|
||||
getPath: function( name ) {
|
||||
var external = this.externals[ name ];
|
||||
return CKEDITOR.getUrl( ( external && external.dir ) || this.basePath + name + '/' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the file path for a specific loaded resource.
|
||||
*
|
||||
* alert( CKEDITOR.plugins.getFilePath( 'sample' ) ); // '<editor path>/plugins/sample/plugin.js'
|
||||
*
|
||||
* @param {String} name The resource name.
|
||||
* @returns {String}
|
||||
*/
|
||||
getFilePath: function( name ) {
|
||||
var external = this.externals[ name ];
|
||||
return CKEDITOR.getUrl( this.getPath( name ) + ( ( external && ( typeof external.file == 'string' ) ) ? external.file : this.fileName + '.js' ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers one or more resources to be loaded from an external path
|
||||
* instead of the core base path.
|
||||
*
|
||||
* // Loads a plugin from '/myplugin/samples/plugin.js'.
|
||||
* CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/' );
|
||||
*
|
||||
* // Loads a plugin from '/myplugin/samples/my_plugin.js'.
|
||||
* CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/', 'my_plugin.js' );
|
||||
*
|
||||
* // Loads a plugin from '/myplugin/samples/my_plugin.js'.
|
||||
* CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/my_plugin.js', '' );
|
||||
*
|
||||
* @param {String} names The resource names, separated by commas.
|
||||
* @param {String} path The path of the folder containing the resource.
|
||||
* @param {String} [fileName] The resource file name. If not provided, the
|
||||
* default name is used. If provided with a empty string, will implicitly indicates that `path` argument
|
||||
* is already the full path.
|
||||
*/
|
||||
addExternal: function( names, path, fileName ) {
|
||||
names = names.split( ',' );
|
||||
for ( var i = 0; i < names.length; i++ ) {
|
||||
var name = names[ i ];
|
||||
|
||||
this.externals[ name ] = {
|
||||
dir: path,
|
||||
file: fileName
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads one or more resources.
|
||||
*
|
||||
* CKEDITOR.plugins.load( 'myplugin', function( plugins ) {
|
||||
* alert( plugins[ 'myplugin' ] ); // object
|
||||
* } );
|
||||
*
|
||||
* @param {String/Array} name The name of the resource to load. It may be a
|
||||
* string with a single resource name, or an array with several names.
|
||||
* @param {Function} callback A function to be called when all resources
|
||||
* are loaded. The callback will receive an array containing all loaded names.
|
||||
* @param {Object} [scope] The scope object to be used for the callback call.
|
||||
*/
|
||||
load: function( names, callback, scope ) {
|
||||
// Ensure that we have an array of names.
|
||||
if ( !CKEDITOR.tools.isArray( names ) )
|
||||
names = names ? [ names ] : [];
|
||||
|
||||
var loaded = this.loaded,
|
||||
registered = this.registered,
|
||||
urls = [],
|
||||
urlsNames = {},
|
||||
resources = {};
|
||||
|
||||
// Loop through all names.
|
||||
for ( var i = 0; i < names.length; i++ ) {
|
||||
var name = names[ i ];
|
||||
|
||||
if ( !name )
|
||||
continue;
|
||||
|
||||
// If not available yet.
|
||||
if ( !loaded[ name ] && !registered[ name ] ) {
|
||||
var url = this.getFilePath( name );
|
||||
urls.push( url );
|
||||
if ( !( url in urlsNames ) )
|
||||
urlsNames[ url ] = [];
|
||||
urlsNames[ url ].push( name );
|
||||
} else
|
||||
resources[ name ] = this.get( name );
|
||||
}
|
||||
|
||||
CKEDITOR.scriptLoader.load( urls, function( completed, failed ) {
|
||||
if ( failed.length ) {
|
||||
throw '[CKEDITOR.resourceManager.load] Resource name "' + urlsNames[ failed[ 0 ] ].join( ',' )
|
||||
+ '" was not found at "' + failed[ 0 ] + '".';
|
||||
}
|
||||
|
||||
for ( var i = 0; i < completed.length; i++ ) {
|
||||
var nameList = urlsNames[ completed[ i ] ];
|
||||
for ( var j = 0; j < nameList.length; j++ ) {
|
||||
var name = nameList[ j ];
|
||||
resources[ name ] = this.get( name );
|
||||
|
||||
loaded[ name ] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
callback.call( scope, resources );
|
||||
}, this );
|
||||
}
|
||||
};
|
||||
202
lib/ckeditor4/core/scriptloader.js
Executable file
202
lib/ckeditor4/core/scriptloader.js
Executable file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.scriptLoader} object, used to load scripts
|
||||
* asynchronously.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load scripts asynchronously.
|
||||
*
|
||||
* @class
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.scriptLoader = (function() {
|
||||
var uniqueScripts = {},
|
||||
waitingList = {};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Loads one or more external script checking if not already loaded
|
||||
* previously by this function.
|
||||
*
|
||||
* CKEDITOR.scriptLoader.load( '/myscript.js' );
|
||||
*
|
||||
* CKEDITOR.scriptLoader.load( '/myscript.js', function( success ) {
|
||||
* // Alerts true if the script has been properly loaded.
|
||||
* // HTTP error 404 should return false.
|
||||
* alert( success );
|
||||
* } );
|
||||
*
|
||||
* CKEDITOR.scriptLoader.load( [ '/myscript1.js', '/myscript2.js' ], function( completed, failed ) {
|
||||
* alert( 'Number of scripts loaded: ' + completed.length );
|
||||
* alert( 'Number of failures: ' + failed.length );
|
||||
* } );
|
||||
*
|
||||
* @param {String/Array} scriptUrl One or more URLs pointing to the
|
||||
* scripts to be loaded.
|
||||
* @param {Function} [callback] A function to be called when the script
|
||||
* is loaded and executed. If a string is passed to `scriptUrl`, a
|
||||
* boolean parameter is passed to the callback, indicating the
|
||||
* success of the load. If an array is passed instead, two arrays
|
||||
* parameters are passed to the callback - the first contains the
|
||||
* URLs that have been properly loaded and the second the failed ones.
|
||||
* @param {Object} [scope] The scope (`this` reference) to be used for
|
||||
* the callback call. Defaults to {@link CKEDITOR}.
|
||||
* @param {Boolean} [showBusy] Changes the cursor of the document while
|
||||
* the script is loaded.
|
||||
*/
|
||||
load: function( scriptUrl, callback, scope, showBusy ) {
|
||||
var isString = ( typeof scriptUrl == 'string' );
|
||||
|
||||
if ( isString )
|
||||
scriptUrl = [ scriptUrl ];
|
||||
|
||||
if ( !scope )
|
||||
scope = CKEDITOR;
|
||||
|
||||
var scriptCount = scriptUrl.length,
|
||||
completed = [],
|
||||
failed = [];
|
||||
|
||||
var doCallback = function( success ) {
|
||||
if ( callback ) {
|
||||
if ( isString )
|
||||
callback.call( scope, success );
|
||||
else
|
||||
callback.call( scope, completed, failed );
|
||||
}
|
||||
};
|
||||
|
||||
if ( scriptCount === 0 ) {
|
||||
doCallback( true );
|
||||
return;
|
||||
}
|
||||
|
||||
var checkLoaded = function( url, success ) {
|
||||
( success ? completed : failed ).push( url );
|
||||
|
||||
if ( --scriptCount <= 0 ) {
|
||||
showBusy && CKEDITOR.document.getDocumentElement().removeStyle( 'cursor' );
|
||||
doCallback( success );
|
||||
}
|
||||
};
|
||||
|
||||
var onLoad = function( url, success ) {
|
||||
// Mark this script as loaded.
|
||||
uniqueScripts[ url ] = 1;
|
||||
|
||||
// Get the list of callback checks waiting for this file.
|
||||
var waitingInfo = waitingList[ url ];
|
||||
delete waitingList[ url ];
|
||||
|
||||
// Check all callbacks waiting for this file.
|
||||
for ( var i = 0; i < waitingInfo.length; i++ )
|
||||
waitingInfo[ i ]( url, success );
|
||||
};
|
||||
|
||||
var loadScript = function( url ) {
|
||||
if ( uniqueScripts[ url ] ) {
|
||||
checkLoaded( url, true );
|
||||
return;
|
||||
}
|
||||
|
||||
var waitingInfo = waitingList[ url ] || ( waitingList[ url ] = [] );
|
||||
waitingInfo.push( checkLoaded );
|
||||
|
||||
// Load it only for the first request.
|
||||
if ( waitingInfo.length > 1 )
|
||||
return;
|
||||
|
||||
// Create the <script> element.
|
||||
var script = new CKEDITOR.dom.element( 'script' );
|
||||
script.setAttributes({
|
||||
type: 'text/javascript',
|
||||
src: url } );
|
||||
|
||||
if ( callback ) {
|
||||
if ( CKEDITOR.env.ie ) {
|
||||
// FIXME: For IE, we are not able to return false on error (like 404).
|
||||
script.$.onreadystatechange = function() {
|
||||
if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' ) {
|
||||
script.$.onreadystatechange = null;
|
||||
onLoad( url, true );
|
||||
}
|
||||
};
|
||||
} else {
|
||||
script.$.onload = function() {
|
||||
// Some browsers, such as Safari, may call the onLoad function
|
||||
// immediately. Which will break the loading sequence. (#3661)
|
||||
setTimeout( function() {
|
||||
onLoad( url, true );
|
||||
}, 0 );
|
||||
};
|
||||
|
||||
// FIXME: Opera and Safari will not fire onerror.
|
||||
script.$.onerror = function() {
|
||||
onLoad( url, false );
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Append it to <head>.
|
||||
script.appendTo( CKEDITOR.document.getHead() );
|
||||
|
||||
CKEDITOR.fire( 'download', url ); // %REMOVE_LINE%
|
||||
};
|
||||
|
||||
showBusy && CKEDITOR.document.getDocumentElement().setStyle( 'cursor', 'wait' );
|
||||
for ( var i = 0; i < scriptCount; i++ ) {
|
||||
loadScript( scriptUrl[ i ] );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a script in a queue, so only one is loaded at the same time.
|
||||
*
|
||||
* @since 4.1.2
|
||||
* @param {String} scriptUrl URL pointing to the script to be loaded.
|
||||
* @param {Function} [callback] A function to be called when the script
|
||||
* is loaded and executed. A boolean parameter is passed to the callback,
|
||||
* indicating the success of the load.
|
||||
*
|
||||
* @see CKEDITOR.scriptLoader#load
|
||||
*/
|
||||
queue: (function() {
|
||||
var pending = [];
|
||||
|
||||
// Loads the very first script from queue and removes it.
|
||||
function loadNext() {
|
||||
var script;
|
||||
|
||||
if ( ( script = pending[ 0 ] ) )
|
||||
this.load( script.scriptUrl, script.callback, CKEDITOR, 0 );
|
||||
}
|
||||
|
||||
return function( scriptUrl, callback ) {
|
||||
var that = this;
|
||||
|
||||
// This callback calls the standard callback for the script
|
||||
// and loads the very next script from pending list.
|
||||
function callbackWrapper() {
|
||||
callback && callback.apply( this, arguments );
|
||||
|
||||
// Removed the just loaded script from the queue.
|
||||
pending.shift();
|
||||
|
||||
loadNext.call( that );
|
||||
}
|
||||
|
||||
// Let's add this script to the queue
|
||||
pending.push( { scriptUrl: scriptUrl, callback: callbackWrapper } );
|
||||
|
||||
// If the queue was empty, then start loading.
|
||||
if ( pending.length == 1 )
|
||||
loadNext.call( this );
|
||||
};
|
||||
})()
|
||||
};
|
||||
})();
|
||||
1764
lib/ckeditor4/core/selection.js
Executable file
1764
lib/ckeditor4/core/selection.js
Executable file
File diff suppressed because it is too large
Load Diff
335
lib/ckeditor4/core/skin.js
Executable file
335
lib/ckeditor4/core/skin.js
Executable file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.skin} class that is used to manage skin parts.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var cssLoaded = {};
|
||||
|
||||
function getName() {
|
||||
return CKEDITOR.skinName.split( ',' )[ 0 ];
|
||||
}
|
||||
|
||||
function getConfigPath() {
|
||||
return CKEDITOR.getUrl( CKEDITOR.skinName.split( ',' )[ 1 ] || ( 'skins/' + getName() + '/' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the loading of skin parts among all editor instances.
|
||||
*
|
||||
* @class
|
||||
* @singleton
|
||||
*/
|
||||
CKEDITOR.skin = {
|
||||
/**
|
||||
* Returns the root path to the skin directory.
|
||||
*
|
||||
* @method
|
||||
* @todo
|
||||
*/
|
||||
path: getConfigPath,
|
||||
|
||||
/**
|
||||
* Loads a skin part into the page. Does nothing if the part has already been loaded.
|
||||
*
|
||||
* **Note:** The "editor" part is always auto loaded upon instance creation,
|
||||
* thus this function is mainly used to **lazy load** other parts of the skin
|
||||
* that do not have to be displayed until requested.
|
||||
*
|
||||
* // Load the dialog part.
|
||||
* editor.skin.loadPart( 'dialog' );
|
||||
*
|
||||
* @param {String} part The name of the skin part CSS file that resides in the skin directory.
|
||||
* @param {Function} fn The provided callback function which is invoked after the part is loaded.
|
||||
*/
|
||||
loadPart: function( part, fn ) {
|
||||
if ( CKEDITOR.skin.name != getName() ) {
|
||||
CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( getConfigPath() + 'skin.js' ), function() {
|
||||
loadCss( part, fn );
|
||||
});
|
||||
} else
|
||||
loadCss( part, fn );
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the real URL of a (CSS) skin part.
|
||||
*
|
||||
* @param {String} part
|
||||
*/
|
||||
getPath: function( part ) {
|
||||
return CKEDITOR.getUrl( getCssPath( part ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* The list of registered icons. To add new icons to this list, use {@link #addIcon}.
|
||||
*/
|
||||
icons: {},
|
||||
|
||||
/**
|
||||
* Registers an icon.
|
||||
*
|
||||
* @param {String} name The icon name.
|
||||
* @param {String} path The path to the icon image file.
|
||||
* @param {Number} [offset] The vertical offset position of the icon, if
|
||||
* available inside a strip image.
|
||||
* @param {String} [bgsize] The value of the CSS "background-size" property to
|
||||
* use for this icon
|
||||
*/
|
||||
addIcon: function( name, path, offset, bgsize ) {
|
||||
name = name.toLowerCase();
|
||||
if ( !this.icons[ name ] ) {
|
||||
this.icons[ name ] = {
|
||||
path: path,
|
||||
offset: offset || 0,
|
||||
bgsize : bgsize || '16px'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the CSS background styles to be used to render a specific icon.
|
||||
*
|
||||
* @param {String} name The icon name, as registered with {@link #addIcon}.
|
||||
* @param {Boolean} [rtl] Indicates that the RTL version of the icon is
|
||||
* to be used, if available.
|
||||
* @param {String} [overridePath] The path to the icon image file. It
|
||||
* overrides the path defined by the named icon, if available, and is
|
||||
* used if the named icon was not registered.
|
||||
* @param {Number} [overrideOffset] The vertical offset position of the
|
||||
* icon. It overrides the offset defined by the named icon, if
|
||||
* available, and is used if the named icon was not registered.
|
||||
* @param {String} [overrideBgsize] The value of the CSS "background-size" property
|
||||
* to use for the icon. It overrides the value defined by the named icon,
|
||||
* if available, and is used if the named icon was not registered.
|
||||
*/
|
||||
getIconStyle: function( name, rtl, overridePath, overrideOffset, overrideBgsize ) {
|
||||
var icon, path, offset, bgsize;
|
||||
|
||||
if ( name ) {
|
||||
name = name.toLowerCase();
|
||||
// If we're in RTL, try to get the RTL version of the icon.
|
||||
if ( rtl )
|
||||
icon = this.icons[ name + '-rtl' ];
|
||||
|
||||
// If not in LTR or no RTL version available, get the generic one.
|
||||
if ( !icon )
|
||||
icon = this.icons[ name ];
|
||||
}
|
||||
|
||||
path = overridePath || ( icon && icon.path ) || '';
|
||||
offset = overrideOffset || ( icon && icon.offset );
|
||||
bgsize = overrideBgsize || ( icon && icon.bgsize ) || '16px';
|
||||
|
||||
return path &&
|
||||
( 'background-image:url(' + CKEDITOR.getUrl( path ) + ');background-position:0 ' + offset + 'px;background-size:' + bgsize + ';' );
|
||||
}
|
||||
};
|
||||
|
||||
function getCssPath( part ) {
|
||||
// Check for ua-specific version of skin part.
|
||||
var uas = CKEDITOR.skin[ 'ua_' + part ], env = CKEDITOR.env;
|
||||
if ( uas ) {
|
||||
|
||||
// Having versioned UA checked first.
|
||||
uas = uas.split( ',' ).sort( function ( a, b ) { return a > b ? -1 : 1; } );
|
||||
|
||||
// Loop through all ua entries, checking is any of them match the current ua.
|
||||
for ( var i = 0, ua; i < uas.length; i++ ) {
|
||||
ua = uas[ i ];
|
||||
|
||||
if ( env.ie ) {
|
||||
if ( ( ua.replace( /^ie/, '' ) == env.version ) || ( env.quirks && ua == 'iequirks' ) )
|
||||
ua = 'ie';
|
||||
}
|
||||
|
||||
if ( env[ ua ] ) {
|
||||
part += '_' + uas[ i ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CKEDITOR.getUrl( getConfigPath() + part + '.css' );
|
||||
}
|
||||
|
||||
function loadCss( part, callback ) {
|
||||
// Avoid reload.
|
||||
if ( !cssLoaded[ part ] ) {
|
||||
CKEDITOR.document.appendStyleSheet( getCssPath( part ) );
|
||||
cssLoaded[ part ] = 1;
|
||||
}
|
||||
|
||||
// CSS loading should not be blocking.
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
CKEDITOR.tools.extend( CKEDITOR.editor.prototype, {
|
||||
/** Gets the color of the editor user interface.
|
||||
*
|
||||
* CKEDITOR.instances.editor1.getUiColor();
|
||||
*
|
||||
* @method
|
||||
* @member CKEDITOR.editor
|
||||
* @returns {String} uiColor The editor UI color or `undefined` if the UI color is not set.
|
||||
*/
|
||||
getUiColor: function() {
|
||||
return this.uiColor;
|
||||
},
|
||||
|
||||
/** Sets the color of the editor user interface. This method accepts a color value in
|
||||
* hexadecimal notation, with a `#` character (e.g. #ffffff).
|
||||
*
|
||||
* CKEDITOR.instances.editor1.setUiColor( '#ff00ff' );
|
||||
*
|
||||
* @method
|
||||
* @member CKEDITOR.editor
|
||||
* @param {String} color The desired editor UI color in hexadecimal notation.
|
||||
*/
|
||||
setUiColor: function( color ) {
|
||||
var uiStyle = getStylesheet( CKEDITOR.document );
|
||||
|
||||
return ( this.setUiColor = function( color ) {
|
||||
var chameleon = CKEDITOR.skin.chameleon;
|
||||
|
||||
var replace = [ [ uiColorRegexp, color ] ];
|
||||
this.uiColor = color;
|
||||
|
||||
// Update general style.
|
||||
updateStylesheets( [ uiStyle ], chameleon( this, 'editor' ), replace );
|
||||
|
||||
// Update panel styles.
|
||||
updateStylesheets( uiColorMenus, chameleon( this, 'panel' ), replace );
|
||||
}).call( this, color );
|
||||
}
|
||||
});
|
||||
|
||||
var uiColorStylesheetId = 'cke_ui_color',
|
||||
uiColorMenus = [],
|
||||
uiColorRegexp = /\$color/g;
|
||||
|
||||
function getStylesheet( document ) {
|
||||
var node = document.getById( uiColorStylesheetId );
|
||||
if ( !node ) {
|
||||
node = document.getHead().append( 'style' );
|
||||
node.setAttribute( "id", uiColorStylesheetId );
|
||||
node.setAttribute( "type", "text/css" );
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function updateStylesheets( styleNodes, styleContent, replace ) {
|
||||
var r, i, content;
|
||||
|
||||
// We have to split CSS declarations for webkit.
|
||||
if ( CKEDITOR.env.webkit ) {
|
||||
styleContent = styleContent.split( '}' ).slice( 0, -1 );
|
||||
for ( i = 0; i < styleContent.length; i++ )
|
||||
styleContent[ i ] = styleContent[ i ].split( '{' );
|
||||
}
|
||||
|
||||
for ( var id = 0; id < styleNodes.length; id++ ) {
|
||||
if ( CKEDITOR.env.webkit ) {
|
||||
for ( i = 0; i < styleContent.length; i++ ) {
|
||||
content = styleContent[ i ][ 1 ];
|
||||
for ( r = 0; r < replace.length; r++ )
|
||||
content = content.replace( replace[ r ][ 0 ], replace[ r ][ 1 ] );
|
||||
|
||||
styleNodes[ id ].$.sheet.addRule( styleContent[ i ][ 0 ], content );
|
||||
}
|
||||
} else {
|
||||
content = styleContent;
|
||||
for ( r = 0; r < replace.length; r++ )
|
||||
content = content.replace( replace[ r ][ 0 ], replace[ r ][ 1 ] );
|
||||
|
||||
if ( CKEDITOR.env.ie )
|
||||
styleNodes[ id ].$.styleSheet.cssText += content;
|
||||
else
|
||||
styleNodes[ id ].$.innerHTML += content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CKEDITOR.on( 'instanceLoaded', function( evt ) {
|
||||
// The chameleon feature is not for IE quirks.
|
||||
if ( CKEDITOR.env.ie && CKEDITOR.env.quirks )
|
||||
return;
|
||||
|
||||
var editor = evt.editor,
|
||||
showCallback = function( event ) {
|
||||
var panel = event.data[ 0 ] || event.data;
|
||||
var iframe = panel.element.getElementsByTag( 'iframe' ).getItem( 0 ).getFrameDocument();
|
||||
|
||||
// Add stylesheet if missing.
|
||||
if ( !iframe.getById( 'cke_ui_color' ) ) {
|
||||
var node = getStylesheet( iframe );
|
||||
uiColorMenus.push( node );
|
||||
|
||||
var color = editor.getUiColor();
|
||||
// Set uiColor for new panel.
|
||||
if ( color ) {
|
||||
updateStylesheets( [ node ], CKEDITOR.skin.chameleon( editor, 'panel' ), [ [ uiColorRegexp, color ] ] );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
editor.on( 'panelShow', showCallback );
|
||||
editor.on( 'menuShow', showCallback );
|
||||
|
||||
// Apply UI color if specified in config.
|
||||
if ( editor.config.uiColor )
|
||||
editor.setUiColor( editor.config.uiColor );
|
||||
});
|
||||
})();
|
||||
|
||||
/**
|
||||
* The list of file names matching the browser user agent string from
|
||||
* {@link CKEDITOR.env}. This is used to load the skin part file in addition
|
||||
* to the "main" skin file for a particular browser.
|
||||
*
|
||||
* **Note:** For each of the defined skin parts the corresponding
|
||||
* CSS file with the same name as the user agent must exist inside
|
||||
* the skin directory.
|
||||
*
|
||||
* @property ua
|
||||
* @todo type?
|
||||
*/
|
||||
|
||||
/**
|
||||
* The name of the skin that is currently used.
|
||||
*
|
||||
* @property {String} name
|
||||
* @todo
|
||||
*/
|
||||
|
||||
/**
|
||||
* The editor skin name. Note that it is not possible to have editors with
|
||||
* different skin settings in the same page. In such case just one of the
|
||||
* skins will be used for all editors.
|
||||
*
|
||||
* This is a shortcut to {@link CKEDITOR#skinName}.
|
||||
*
|
||||
* It is possible to install skins outside the default `skin` folder in the
|
||||
* editor installation. In that case, the absolute URL path to that folder
|
||||
* should be provided, separated by a comma (`'skin_name,skin_path'`).
|
||||
*
|
||||
* config.skin = 'moono';
|
||||
*
|
||||
* config.skin = 'myskin,/customstuff/myskin/';
|
||||
*
|
||||
* @cfg {String} skin
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function that supports the chameleon (skin color switch) feature, providing
|
||||
* the skin color style updates to be applied in runtime.
|
||||
*
|
||||
* **Note:** The embedded `$color` variable is to be substituted with a specific UI color.
|
||||
*
|
||||
* @method chameleon
|
||||
* @param {String} editor The editor instance that the color changes apply to.
|
||||
* @param {String} part The name of the skin part where the color changes take place.
|
||||
*/
|
||||
1594
lib/ckeditor4/core/style.js
Executable file
1594
lib/ckeditor4/core/style.js
Executable file
File diff suppressed because it is too large
Load Diff
62
lib/ckeditor4/core/template.js
Executable file
62
lib/ckeditor4/core/template.js
Executable file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.template} class, which represents
|
||||
* an UI template for an editor instance.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Lightweight template used to build the output string from variables.
|
||||
*
|
||||
* // HTML template for presenting a label UI.
|
||||
* var tpl = new CKEDITOR.template( '<div class="{cls}">{label}</div>' );
|
||||
* alert( tpl.output( { cls: 'cke-label', label: 'foo'} ) ); // '<div class="cke-label">foo</div>'
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a template class instance.
|
||||
* @param {String} source The template source.
|
||||
*/
|
||||
CKEDITOR.template = function( source ) {
|
||||
// Builds an optimized function body for the output() method, focused on performance.
|
||||
// For example, if we have this "source":
|
||||
// '<div style="{style}">{editorName}</div>'
|
||||
// ... the resulting function body will be (apart from the "buffer" handling):
|
||||
// return [ '<div style="', data['style'] == undefined ? '{style}' : data['style'], '">', data['editorName'] == undefined ? '{editorName}' : data['editorName'], '</div>' ].join('');
|
||||
|
||||
// Try to read from the cache.
|
||||
if ( cache[ source ] )
|
||||
this.output = cache[ source ];
|
||||
else {
|
||||
var fn = source
|
||||
// Escape all quotation marks (").
|
||||
.replace( /'/g, "\\'" )
|
||||
// Inject the template keys replacement.
|
||||
.replace( /{([^}]+)}/g, function( m, key ) {
|
||||
return "',data['" + key + "']==undefined?'{" + key + "}':data['" + key + "'],'";
|
||||
});
|
||||
|
||||
fn = "return buffer?buffer.push('" + fn + "'):['" + fn + "'].join('');";
|
||||
this.output = cache[ source ] = Function( 'data', 'buffer', fn );
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Processes the template, filling its variables with the provided data.
|
||||
*
|
||||
* @method output
|
||||
* @param {Object} data An object containing properties which values will be
|
||||
* used to fill the template variables. The property names must match the
|
||||
* template variables names. Variables without matching properties will be
|
||||
* kept untouched.
|
||||
* @param {Array} [buffer] An array into which the output data will be pushed into.
|
||||
* The number of entries appended to the array is unknown.
|
||||
* @returns {String/Number} If `buffer` has not been provided, the processed
|
||||
* template output data, otherwise the new length of `buffer`.
|
||||
*/
|
||||
1019
lib/ckeditor4/core/tools.js
Executable file
1019
lib/ckeditor4/core/tools.js
Executable file
File diff suppressed because it is too large
Load Diff
168
lib/ckeditor4/core/ui.js
Executable file
168
lib/ckeditor4/core/ui.js
Executable file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains UI features related to an editor instance.
|
||||
*
|
||||
* @class
|
||||
* @mixins CKEDITOR.event
|
||||
* @constructor Creates an ui class instance.
|
||||
* @param {CKEDITOR.editor} editor The editor instance.
|
||||
*/
|
||||
CKEDITOR.ui = function( editor ) {
|
||||
if ( editor.ui )
|
||||
return editor.ui;
|
||||
|
||||
this.items = {};
|
||||
this.instances = {};
|
||||
this.editor = editor;
|
||||
|
||||
/**
|
||||
* Object used to hold private stuff.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this._ = {
|
||||
handlers: {}
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// PACKAGER_RENAME( CKEDITOR.ui )
|
||||
|
||||
CKEDITOR.ui.prototype = {
|
||||
/**
|
||||
* Adds a UI item to the items collection. These items can be later used in
|
||||
* the interface.
|
||||
*
|
||||
* // Add a new button named 'MyBold'.
|
||||
* editorInstance.ui.add( 'MyBold', CKEDITOR.UI_BUTTON, {
|
||||
* label: 'My Bold',
|
||||
* command: 'bold'
|
||||
* } );
|
||||
*
|
||||
* @param {String} name The UI item name.
|
||||
* @param {Object} type The item type.
|
||||
* @param {Object} definition The item definition. The properties of this
|
||||
* object depend on the item type.
|
||||
*/
|
||||
add: function( name, type, definition ) {
|
||||
// Compensate the unique name of this ui item to definition.
|
||||
definition.name = name.toLowerCase();
|
||||
|
||||
var item = this.items[ name ] = {
|
||||
type: type,
|
||||
// The name of {@link CKEDITOR.command} which associate with this UI.
|
||||
command: definition.command || null,
|
||||
args: Array.prototype.slice.call( arguments, 2 )
|
||||
};
|
||||
|
||||
CKEDITOR.tools.extend( item, definition );
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the created ui objects by name.
|
||||
*
|
||||
* @param {String} name The name of the UI definition.
|
||||
*/
|
||||
get: function( name ) {
|
||||
return this.instances[ name ];
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a UI object.
|
||||
*
|
||||
* @param {String} name The UI item hame.
|
||||
* @returns {Object} The UI element.
|
||||
*/
|
||||
create: function( name ) {
|
||||
var item = this.items[ name ],
|
||||
handler = item && this._.handlers[ item.type ],
|
||||
command = item && item.command && this.editor.getCommand( item.command );
|
||||
|
||||
var result = handler && handler.create.apply( this, item.args );
|
||||
|
||||
this.instances[ name ] = result;
|
||||
|
||||
// Add reference inside command object.
|
||||
if ( command )
|
||||
command.uiItems.push( result );
|
||||
|
||||
if ( result && !result.type )
|
||||
result.type = item.type;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a handler for a UI item type. The handler is responsible for
|
||||
* transforming UI item definitions in UI objects.
|
||||
*
|
||||
* @param {Object} type The item type.
|
||||
* @param {Object} handler The handler definition.
|
||||
*/
|
||||
addHandler: function( type, handler ) {
|
||||
this._.handlers[ type ] = handler;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the unique DOM element that represents one editor's UI part, as
|
||||
* the editor UI is made completely decoupled from DOM (no DOM reference hold),
|
||||
* this method is mainly used to retrieve the rendered DOM part by name.
|
||||
*
|
||||
* // Hide the bottom space in the UI.
|
||||
* var bottom = editor.ui.getSpace( 'bottom' );
|
||||
* bottom.setStyle( 'display', 'none' );
|
||||
*
|
||||
* @param {String} name The space name.
|
||||
* @returns {CKEDITOR.dom.element} The element that represents the space.
|
||||
*/
|
||||
space: function( name ) {
|
||||
return CKEDITOR.document.getById( this.spaceId( name ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate the HTML ID from a specific UI space name.
|
||||
*
|
||||
* @param name
|
||||
* @todo param and return types?
|
||||
*/
|
||||
spaceId: function( name ) {
|
||||
return this.editor.id + '_' + name;
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.event.implementOn( CKEDITOR.ui );
|
||||
|
||||
/**
|
||||
* Internal event fired when a new UI element is ready.
|
||||
*
|
||||
* @event ready
|
||||
* @param {Object} data The new element.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Virtual class which just illustrates the features of handler objects to be
|
||||
* passed to the {@link CKEDITOR.ui#addHandler} function.
|
||||
* This class is not really part of the API, so don't call its constructor.
|
||||
*
|
||||
* @class CKEDITOR.ui.handlerDefinition
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms an item definition into an UI item object.
|
||||
*
|
||||
* editorInstance.ui.addHandler( CKEDITOR.UI_BUTTON, {
|
||||
* create: function( definition ) {
|
||||
* return new CKEDITOR.ui.button( definition );
|
||||
* }
|
||||
* } );
|
||||
*
|
||||
* @method create
|
||||
* @param {Object} definition The item definition.
|
||||
* @returns {Object} The UI element.
|
||||
* @todo We lack the "UI element" abstract super class.
|
||||
*/
|
||||
63
lib/ckeditor4/lang/_translationstatus.txt
Executable file
63
lib/ckeditor4/lang/_translationstatus.txt
Executable file
@@ -0,0 +1,63 @@
|
||||
Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
|
||||
af.js Found: 62 Missing: 4
|
||||
ar.js Found: 51 Missing: 15
|
||||
bg.js Found: 58 Missing: 8
|
||||
bn.js Found: 40 Missing: 26
|
||||
bs.js Found: 29 Missing: 37
|
||||
ca.js Found: 61 Missing: 5
|
||||
cs.js Found: 66 Missing: 0
|
||||
cy.js Found: 66 Missing: 0
|
||||
da.js Found: 66 Missing: 0
|
||||
de.js Found: 66 Missing: 0
|
||||
el.js Found: 59 Missing: 7
|
||||
en-au.js Found: 38 Missing: 28
|
||||
en-ca.js Found: 37 Missing: 29
|
||||
en-gb.js Found: 61 Missing: 5
|
||||
eo.js Found: 66 Missing: 0
|
||||
es.js Found: 66 Missing: 0
|
||||
et.js Found: 66 Missing: 0
|
||||
eu.js Found: 48 Missing: 18
|
||||
fa.js Found: 66 Missing: 0
|
||||
fi.js Found: 66 Missing: 0
|
||||
fo.js Found: 66 Missing: 0
|
||||
fr-ca.js Found: 42 Missing: 24
|
||||
fr.js Found: 66 Missing: 0
|
||||
gl.js Found: 40 Missing: 26
|
||||
gu.js Found: 66 Missing: 0
|
||||
he.js Found: 66 Missing: 0
|
||||
hi.js Found: 43 Missing: 23
|
||||
hr.js Found: 66 Missing: 0
|
||||
hu.js Found: 63 Missing: 3
|
||||
is.js Found: 41 Missing: 25
|
||||
it.js Found: 66 Missing: 0
|
||||
ja.js Found: 62 Missing: 4
|
||||
ka.js Found: 62 Missing: 4
|
||||
km.js Found: 40 Missing: 26
|
||||
ko.js Found: 40 Missing: 26
|
||||
lt.js Found: 66 Missing: 0
|
||||
lv.js Found: 40 Missing: 26
|
||||
mk.js Found: 0 Missing: 66
|
||||
mn.js Found: 40 Missing: 26
|
||||
ms.js Found: 39 Missing: 27
|
||||
nb.js Found: 66 Missing: 0
|
||||
nl.js Found: 65 Missing: 1
|
||||
no.js Found: 66 Missing: 0
|
||||
pl.js Found: 66 Missing: 0
|
||||
pt-br.js Found: 66 Missing: 0
|
||||
pt.js Found: 52 Missing: 14
|
||||
ro.js Found: 61 Missing: 5
|
||||
ru.js Found: 66 Missing: 0
|
||||
sk.js Found: 49 Missing: 17
|
||||
sl.js Found: 48 Missing: 18
|
||||
sr-latn.js Found: 40 Missing: 26
|
||||
sr.js Found: 40 Missing: 26
|
||||
sv.js Found: 62 Missing: 4
|
||||
th.js Found: 40 Missing: 26
|
||||
tr.js Found: 66 Missing: 0
|
||||
ug.js Found: 66 Missing: 0
|
||||
uk.js Found: 66 Missing: 0
|
||||
vi.js Found: 66 Missing: 0
|
||||
zh-cn.js Found: 66 Missing: 0
|
||||
zh.js Found: 58 Missing: 8
|
||||
105
lib/ckeditor4/lang/af.js
Executable file
105
lib/ckeditor4/lang/af.js
Executable file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.lang} object, for the
|
||||
* Afrikaans language.
|
||||
*/
|
||||
|
||||
/**#@+
|
||||
@type String
|
||||
@example
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains the dictionary of language entries.
|
||||
* @namespace
|
||||
*/
|
||||
CKEDITOR.lang[ 'af' ] = {
|
||||
/**
|
||||
* The language reading direction. Possible values are "rtl" for
|
||||
* Right-To-Left languages (like Arabic) and "ltr" for Left-To-Right
|
||||
* languages (like English).
|
||||
* @default 'ltr'
|
||||
*/
|
||||
dir: 'ltr',
|
||||
|
||||
// ARIA description.
|
||||
editor: 'Teksverwerker',
|
||||
|
||||
// Common messages and labels.
|
||||
common: {
|
||||
// Screenreader titles. Please note that screenreaders are not always capable
|
||||
// of reading non-English words. So be careful while translating it.
|
||||
editorHelp: 'Druk op ALT 0 vir hulp',
|
||||
|
||||
browseServer: 'Blaai op bediener',
|
||||
url: 'URL',
|
||||
protocol: 'Protokol',
|
||||
upload: 'Oplaai',
|
||||
uploadSubmit: 'Stuur na bediener',
|
||||
image: 'Afbeelding',
|
||||
flash: 'Flash',
|
||||
form: 'Vorm',
|
||||
checkbox: 'Merkhokkie',
|
||||
radio: 'Radioknoppie',
|
||||
textField: 'Teksveld',
|
||||
textarea: 'Teks-area',
|
||||
hiddenField: 'Blinde veld',
|
||||
button: 'Knop',
|
||||
select: 'Keuseveld',
|
||||
imageButton: 'Afbeeldingsknop',
|
||||
notSet: '<geen instelling>',
|
||||
id: 'Id',
|
||||
name: 'Naam',
|
||||
langDir: 'Skryfrigting',
|
||||
langDirLtr: 'Links na regs (LTR)',
|
||||
langDirRtl: 'Regs na links (RTL)',
|
||||
langCode: 'Taalkode',
|
||||
longDescr: 'Lang beskrywing URL',
|
||||
cssClass: 'CSS klasse',
|
||||
advisoryTitle: 'Aanbevole titel',
|
||||
cssStyle: 'Styl',
|
||||
ok: 'OK',
|
||||
cancel: 'Kanselleer',
|
||||
close: 'Sluit',
|
||||
preview: 'Voorbeeld',
|
||||
resize: 'Sleep om te herskaal',
|
||||
generalTab: 'Algemeen',
|
||||
advancedTab: 'Gevorderd',
|
||||
validateNumberFailed: 'Hierdie waarde is nie \'n getal nie.',
|
||||
confirmNewPage: 'Alle wysiginge sal verlore gaan. Is u seker dat u \'n nuwe bladsy wil laai?',
|
||||
confirmCancel: 'Sommige opsies is gewysig. Is u seker dat u hierdie dialoogvenster wil sluit?',
|
||||
options: 'Opsies',
|
||||
target: 'Doel',
|
||||
targetNew: 'Nuwe venster (_blank)',
|
||||
targetTop: 'Boonste venster (_top)',
|
||||
targetSelf: 'Selfde venster (_self)',
|
||||
targetParent: 'Oorspronklike venster (_parent)',
|
||||
langDirLTR: 'Links na Regs (LTR)',
|
||||
langDirRTL: 'Regs na Links (RTL)',
|
||||
styles: 'Styl',
|
||||
cssClasses: 'CSS klasse',
|
||||
width: 'Breedte',
|
||||
height: 'Hoogte',
|
||||
align: 'Oplyn',
|
||||
alignLeft: 'Links',
|
||||
alignRight: 'Regs',
|
||||
alignCenter: 'Sentreer',
|
||||
alignTop: 'Bo',
|
||||
alignMiddle: 'Middel',
|
||||
alignBottom: 'Onder',
|
||||
invalidValue : 'Invalid value.', // MISSING
|
||||
invalidHeight: 'Hoogte moet \'n getal wees',
|
||||
invalidWidth: 'Breedte moet \'n getal wees.',
|
||||
invalidCssLength: 'Value specified for the "%1" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).', // MISSING
|
||||
invalidHtmlLength: 'Value specified for the "%1" field must be a positive number with or without a valid HTML measurement unit (px or %).', // MISSING
|
||||
invalidInlineStyle: 'Value specified for the inline style must consist of one or more tuples with the format of "name : value", separated by semi-colons.', // MISSING
|
||||
cssLengthTooltip: 'Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).', // MISSING
|
||||
|
||||
// Put the voice-only part of the label in the span.
|
||||
unavailable: '%1<span class="cke_accessibility">, nie beskikbaar nie</span>'
|
||||
}
|
||||
};
|
||||
105
lib/ckeditor4/lang/ar.js
Executable file
105
lib/ckeditor4/lang/ar.js
Executable file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.lang} object, for the
|
||||
* Arabic language.
|
||||
*/
|
||||
|
||||
/**#@+
|
||||
@type String
|
||||
@example
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains the dictionary of language entries.
|
||||
* @namespace
|
||||
*/
|
||||
CKEDITOR.lang[ 'ar' ] = {
|
||||
/**
|
||||
* The language reading direction. Possible values are "rtl" for
|
||||
* Right-To-Left languages (like Arabic) and "ltr" for Left-To-Right
|
||||
* languages (like English).
|
||||
* @default 'ltr'
|
||||
*/
|
||||
dir: 'rtl',
|
||||
|
||||
// ARIA description.
|
||||
editor: 'محرر النص الغني',
|
||||
|
||||
// Common messages and labels.
|
||||
common: {
|
||||
// Screenreader titles. Please note that screenreaders are not always capable
|
||||
// of reading non-English words. So be careful while translating it.
|
||||
editorHelp: 'إضغط على ALT + 0 للحصول على المساعدة.',
|
||||
|
||||
browseServer: 'تصفح',
|
||||
url: 'الرابط',
|
||||
protocol: 'البروتوكول',
|
||||
upload: 'رفع',
|
||||
uploadSubmit: 'أرسل',
|
||||
image: 'صورة',
|
||||
flash: 'فلاش',
|
||||
form: 'نموذج',
|
||||
checkbox: 'خانة إختيار',
|
||||
radio: 'زر اختيار',
|
||||
textField: 'مربع نص',
|
||||
textarea: 'مساحة نصية',
|
||||
hiddenField: 'إدراج حقل خفي',
|
||||
button: 'زر ضغط',
|
||||
select: 'اختار',
|
||||
imageButton: 'زر صورة',
|
||||
notSet: '<بدون تحديد>',
|
||||
id: 'الرقم',
|
||||
name: 'إسم',
|
||||
langDir: 'إتجاه النص',
|
||||
langDirLtr: 'اليسار لليمين (LTR)',
|
||||
langDirRtl: 'اليمين لليسار (RTL)',
|
||||
langCode: 'رمز اللغة',
|
||||
longDescr: 'الوصف التفصيلى',
|
||||
cssClass: 'فئات التنسيق',
|
||||
advisoryTitle: 'عنوان التقرير',
|
||||
cssStyle: 'نمط',
|
||||
ok: 'موافق',
|
||||
cancel: 'إلغاء الأمر',
|
||||
close: 'أغلق',
|
||||
preview: 'استعراض',
|
||||
resize: 'تغيير الحجم',
|
||||
generalTab: 'عام',
|
||||
advancedTab: 'متقدم',
|
||||
validateNumberFailed: 'لايوجد نتيجة',
|
||||
confirmNewPage: 'ستفقد أي متغييرات اذا لم تقم بحفظها اولا. هل أنت متأكد أنك تريد صفحة جديدة؟',
|
||||
confirmCancel: 'بعض الخيارات قد تغيرت. هل أنت متأكد من إغلاق مربع النص؟',
|
||||
options: 'خيارات',
|
||||
target: 'هدف الرابط',
|
||||
targetNew: 'نافذة جديدة',
|
||||
targetTop: 'النافذة الأعلى',
|
||||
targetSelf: 'داخل النافذة',
|
||||
targetParent: 'النافذة الأم',
|
||||
langDirLTR: 'اليسار لليمين (LTR)',
|
||||
langDirRTL: 'اليمين لليسار (RTL)',
|
||||
styles: 'نمط',
|
||||
cssClasses: 'فئات التنسيق',
|
||||
width: 'العرض',
|
||||
height: 'الإرتفاع',
|
||||
align: 'محاذاة',
|
||||
alignLeft: 'يسار',
|
||||
alignRight: 'يمين',
|
||||
alignCenter: 'وسط',
|
||||
alignTop: 'أعلى',
|
||||
alignMiddle: 'وسط',
|
||||
alignBottom: 'أسفل',
|
||||
invalidValue : 'قيمة غير مفبولة.',
|
||||
invalidHeight: 'الارتفاع يجب أن يكون عدداً.',
|
||||
invalidWidth: 'العرض يجب أن يكون عدداً.',
|
||||
invalidCssLength: 'قيمة الخانة المخصصة لـ "%1" يجب أن تكون رقما موجبا، باستخدام أو من غير استخدام وحدة CSS قياس مقبولة (px, %, in, cm, mm, em, ex, pt, or pc).',
|
||||
invalidHtmlLength: 'قيمة الخانة المخصصة لـ "%1" يجب أن تكون رقما موجبا، باستخدام أو من غير استخدام وحدة HTML قياس مقبولة (px or %).',
|
||||
invalidInlineStyle: 'قيمة الخانة المخصصة لـ Inline Style يجب أن تختوي على مجموع واحد أو أكثر بالشكل التالي: "name : value", مفصولة بفاصلة منقزطة.',
|
||||
cssLengthTooltip: 'أدخل رقما للقيمة بالبكسل أو رقما بوحدة CSS مقبولة (px, %, in, cm, mm, em, ex, pt, or pc).',
|
||||
|
||||
// Put the voice-only part of the label in the span.
|
||||
unavailable: '%1<span class="cke_accessibility">, غير متاح</span>'
|
||||
}
|
||||
};
|
||||
105
lib/ckeditor4/lang/bg.js
Executable file
105
lib/ckeditor4/lang/bg.js
Executable file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Defines the {@link CKEDITOR.lang} object, for the
|
||||
* Bulgarian language.
|
||||
*/
|
||||
|
||||
/**#@+
|
||||
@type String
|
||||
@example
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains the dictionary of language entries.
|
||||
* @namespace
|
||||
*/
|
||||
CKEDITOR.lang[ 'bg' ] = {
|
||||
/**
|
||||
* The language reading direction. Possible values are "rtl" for
|
||||
* Right-To-Left languages (like Arabic) and "ltr" for Left-To-Right
|
||||
* languages (like English).
|
||||
* @default 'ltr'
|
||||
*/
|
||||
dir: 'ltr',
|
||||
|
||||
// ARIA description.
|
||||
editor: 'Текстов редактор за форматиран текст',
|
||||
|
||||
// Common messages and labels.
|
||||
common: {
|
||||
// Screenreader titles. Please note that screenreaders are not always capable
|
||||
// of reading non-English words. So be careful while translating it.
|
||||
editorHelp: 'натиснете ALT 0 за помощ',
|
||||
|
||||
browseServer: 'Избор от сървъра',
|
||||
url: 'URL',
|
||||
protocol: 'Протокол',
|
||||
upload: 'Качване',
|
||||
uploadSubmit: 'Изпращане към сървъра',
|
||||
image: 'Снимка',
|
||||
flash: 'Флаш',
|
||||
form: 'Форма',
|
||||
checkbox: 'Поле за избор',
|
||||
radio: 'Радио бутон',
|
||||
textField: 'Текстово поле',
|
||||
textarea: 'Текстова зона',
|
||||
hiddenField: 'Скрито поле',
|
||||
button: 'Бутон',
|
||||
select: 'Поле за избор',
|
||||
imageButton: 'Бутон за снимка',
|
||||
notSet: '<не е избрано>',
|
||||
id: 'ID',
|
||||
name: 'Име',
|
||||
langDir: 'Посока на езика',
|
||||
langDirLtr: 'Ляво на дясно (ЛнД)',
|
||||
langDirRtl: 'Дясно на ляво (ДнЛ)',
|
||||
langCode: 'Код на езика',
|
||||
longDescr: 'Уеб адрес за дълго описание',
|
||||
cssClass: 'Класове за CSS',
|
||||
advisoryTitle: 'Препоръчително заглавие',
|
||||
cssStyle: 'Стил',
|
||||
ok: 'ОК',
|
||||
cancel: 'Отказ',
|
||||
close: 'Затвори',
|
||||
preview: 'Преглед',
|
||||
resize: 'Влачете за да оразмерите',
|
||||
generalTab: 'Общи',
|
||||
advancedTab: 'Разширено',
|
||||
validateNumberFailed: 'Тази стойност не е число',
|
||||
confirmNewPage: 'Всички незапазени промени ще бъдат изгубени. Сигурни ли сте, че желаете да заредите нова страница?',
|
||||
confirmCancel: 'Някои от опциите са променени. Сигурни ли сте, че желаете да затворите прозореца?',
|
||||
options: 'Опции',
|
||||
target: 'Цел',
|
||||
targetNew: 'Нов прозорец (_blank)',
|
||||
targetTop: 'Горна позиция (_top)',
|
||||
targetSelf: 'Текущия прозорец (_self)',
|
||||
targetParent: 'Основен прозорец (_parent)',
|
||||
langDirLTR: 'Ляво на дясно (ЛнД)',
|
||||
langDirRTL: 'Дясно на ляво (ДнЛ)',
|
||||
styles: 'Стил',
|
||||
cssClasses: 'Класове за CSS',
|
||||
width: 'Ширина',
|
||||
height: 'Височина',
|
||||
align: 'Подравняване',
|
||||
alignLeft: 'Ляво',
|
||||
alignRight: 'Дясно',
|
||||
alignCenter: 'Център',
|
||||
alignTop: 'Горе',
|
||||
alignMiddle: 'По средата',
|
||||
alignBottom: 'Долу',
|
||||
invalidValue : 'Невалидна стойност.',
|
||||
invalidHeight: 'Височината трябва да е число.',
|
||||
invalidWidth: 'Ширина требе да е число.',
|
||||
invalidCssLength: 'Стойността на полето "%1" трябва да бъде положително число с или без валидна CSS измервателна единица (px, %, in, cm, mm, em, ex, pt, или pc).',
|
||||
invalidHtmlLength: 'Стойността на полето "%1" трябва да бъде положително число с или без валидна HTML измервателна единица (px или %).',
|
||||
invalidInlineStyle: 'Стойността на стилa трябва да съдържат една или повече двойки във формат "name : value", разделени с двоеточие.',
|
||||
cssLengthTooltip: 'Въведете числена стойност в пиксели или друга валидна CSS единица (px, %, in, cm, mm, em, ex, pt, или pc).',
|
||||
|
||||
// Put the voice-only part of the label in the span.
|
||||
unavailable: '%1<span class="cke_accessibility">, недостъпно</span>'
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user