diff --git a/MIGRATE-DEV.md b/MIGRATE-DEV.md index af5eb087da..53950d116c 100644 --- a/MIGRATE-DEV.md +++ b/MIGRATE-DEV.md @@ -24,3 +24,22 @@ Version 1.15 (Unreleased) ### Type restrictions - `\humhub\libs\BaseSettingsManager` and its child classes on fields, method parameters, & return types + +### Removed Deprecations + +- `humhub\modules\content\widgets\richtext\PreviewMarkdown` +- `humhub\modules\content\widgets\richtext\ProsemirrorRichText::replaceLinkExtension` +- `humhub\modules\content\widgets\richtext\ProsemirrorRichText::scanLinkExtension` +- `humhub\modules\content\widgets\richtext\ProsemirrorRichText::parseOutput` +- `humhub\modules\content\widgets\richtext\AbstractRichText::$minimal` +- `humhub\modules\content\widgets\richtext\AbstractRichText::$maxLength` +- `humhub\modules\content\widgets\richtext\AbstractRichText::$markdown` +- `humhub\libs\Markdown` +- `humhub\libs\MarkdownPreview` +- `humhub\widgets\MarkdownEditor` +- `humhub\widgets\MarkdownField` +- `humhub\widgets\MarkdownFieldModals` +- `humhub\modules\ui\form\widgets\Markdown` +- + + diff --git a/composer.json b/composer.json index 30a3fe3205..090287e3d2 100644 --- a/composer.json +++ b/composer.json @@ -32,11 +32,9 @@ "matthewbdaly/zendsearch": "^0.0.3", "mistic100/randomcolor": "^1.0", "npm-asset/animate.css": "^4.0", - "npm-asset/at.js": "^1.5.1", "npm-asset/bluebird": "^3.3.5", "npm-asset/blueimp-file-upload": "^9.24", "npm-asset/blueimp-gallery": "^2.36.0", - "npm-asset/bootstrap-markdown": "^2.10", "npm-asset/bootstrap-tour": "^0.11.0", "npm-asset/clipboard-polyfill": "^3.0", "npm-asset/codemirror": "^5.59", diff --git a/composer.lock b/composer.lock index 9ead6cf868..7d64bd1e81 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f180ea8010eb5b8d645d6a8c6a253398", + "content-hash": "a5fb2a55a8202dc41d650e61eb20838f", "packages": [ { "name": "async-aws/core", @@ -3504,18 +3504,6 @@ "MIT" ] }, - { - "name": "npm-asset/at.js", - "version": "1.5.4", - "dist": { - "type": "tar", - "url": "https://registry.npmjs.org/at.js/-/at.js-1.5.4.tgz" - }, - "type": "npm-asset", - "license": [ - "MIT" - ] - }, { "name": "npm-asset/backo2", "version": "1.0.2", @@ -3638,18 +3626,6 @@ "MIT" ] }, - { - "name": "npm-asset/bootstrap-markdown", - "version": "2.10.0", - "dist": { - "type": "tar", - "url": "https://registry.npmjs.org/bootstrap-markdown/-/bootstrap-markdown-2.10.0.tgz" - }, - "type": "npm-asset", - "license": [ - "Apache-2.0" - ] - }, { "name": "npm-asset/bootstrap-tour", "version": "0.11.0", @@ -10968,17 +10944,17 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "237f1821ece806de66072813d8cbe5bdbc8f3117" + "reference": "9920192eab87b9ae105696b0b86326c8fac91677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/237f1821ece806de66072813d8cbe5bdbc8f3117", - "reference": "237f1821ece806de66072813d8cbe5bdbc8f3117", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9920192eab87b9ae105696b0b86326c8fac91677", + "reference": "9920192eab87b9ae105696b0b86326c8fac91677", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.2.8", + "admidio/admidio": "<4.2.9", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<=2.2.1", "akaunting/akaunting": "<2.1.13", @@ -11012,7 +10988,7 @@ "baserproject/basercms": "<4.7.5", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", - "billz/raspap-webgui": "<=2.6.6", + "billz/raspap-webgui": "<2.8.9", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", "bmarshall511/wordpress_zero_spam": "<5.2.13", "bolt/bolt": "<3.7.2", @@ -11241,7 +11217,7 @@ "modx/revolution": "<= 2.8.3-pl|<2.8", "mojo42/jirafeau": "<4.4", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.2-rc.2|= 3.11", + "moodle/moodle": "<4.2-rc.2|= 4.2.0|= 3.11", "mustache/mustache": ">=2,<2.14.1", "namshi/jose": "<2.2", "neoan3-apps/template": "<1.1.1", @@ -11349,7 +11325,7 @@ "shopware/core": "<=6.4.20", "shopware/platform": "<=6.4.20", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.14", + "shopware/shopware": "<=5.7.17", "shopware/storefront": "<=6.4.8.1", "shopxo/shopxo": "<2.2.6", "showdoc/showdoc": "<2.10.4", @@ -11576,7 +11552,7 @@ "type": "tidelift" } ], - "time": "2023-06-22T20:04:46+00:00" + "time": "2023-06-28T23:04:41+00:00" }, { "name": "softcreatr/jsonpath", diff --git a/protected/humhub/assets/AtJsAsset.php b/protected/humhub/assets/AtJsAsset.php deleted file mode 100644 index 0143fc9d05..0000000000 --- a/protected/humhub/assets/AtJsAsset.php +++ /dev/null @@ -1,40 +0,0 @@ - 'IE 9' - ]; - -} diff --git a/protected/humhub/assets/IEFixesAsset.php b/protected/humhub/assets/IEFixesAsset.php deleted file mode 100644 index 7afe275f59..0000000000 --- a/protected/humhub/assets/IEFixesAsset.php +++ /dev/null @@ -1,54 +0,0 @@ - 'lt IE 9' - ]; - - /** - * @inheritdoc - */ - public $depends = [ - 'humhub\assets\Html5shivAsset', - ]; - -} diff --git a/protected/humhub/assets/UIFormAsset.php b/protected/humhub/assets/UIFormAsset.php deleted file mode 100644 index 3bc582a9ea..0000000000 --- a/protected/humhub/assets/UIFormAsset.php +++ /dev/null @@ -1,32 +0,0 @@ - \yii\web\View::POS_END]; - - public $basePath = '@webroot-static'; - public $baseUrl = '@web-static'; - - /** - * @inheritdoc - */ - public $js = ['js/humhub/humhub.ui.form.js']; - -} diff --git a/protected/humhub/controllers/MarkdownController.php b/protected/humhub/controllers/MarkdownController.php deleted file mode 100644 index ae27d0becc..0000000000 --- a/protected/humhub/controllers/MarkdownController.php +++ /dev/null @@ -1,43 +0,0 @@ - [ - 'class' => AccessControl::class, - ] - ]; - } - - public function actionPreview() - { - $this->forcePostRequest(); - - return MarkdownView::widget(['markdown' => Yii::$app->request->post('markdown')]); - } -} diff --git a/protected/humhub/libs/Markdown.php b/protected/humhub/libs/Markdown.php deleted file mode 100644 index dcc7face49..0000000000 --- a/protected/humhub/libs/Markdown.php +++ /dev/null @@ -1,143 +0,0 @@ - $guid]); - if ($file !== null) { - return $file->getUrl(); - } - } - - return $url; - } - - protected function renderLink($block) - { - if (isset($block['refkey'])) { - if (($ref = $this->lookupReference($block['refkey'])) !== false) { - $block = array_merge($block, $ref); - } else { - return $block['orig']; - } - } - - $block['url'] = $this->handleInternalUrls($block['url']); - - $baseUrl = Url::base(true); - - $url = (empty($block['url'])) ? $baseUrl : $block['url']; - - return Html::a($this->renderAbsy($block['text']), Html::decode($url), [ - 'target' => '_blank' - ]); - } - - protected function renderImage($block) - { - if (isset($block['refkey'])) { - if (($ref = $this->lookupReference($block['refkey'])) !== false) { - $block = array_merge($block, $ref); - } else { - return $block['orig']; - } - } - - $block['url'] = $this->handleInternalUrls($block['url']); - - return '' . htmlspecialchars($block['text'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . 'html5 ? '>' : ' />'); - } - - protected function renderAutoUrl($block) - { - return Html::a($block[1], $block[1], ['target' => '_blank']); - } - - /** - * Renders a code block - * @param $block - * @return string - */ - protected function renderCode($block) - { - $class = isset($block['language']) ? ' class="' . Html::encode($block['language']) . '"' : ''; - - return "
" . $block['content'] . "\n" . "
\n"; - } - - /** - * "Dirty" hacked LinkTrait - * - * Try to allow also wiki urls with whitespaces etc. - * @param $markdown - * @return array|bool - */ - protected function parseLinkOrImage($markdown) - { - if (strpos($markdown, ']') !== false && preg_match('/\[((?>[^\]\[]+|(?R))*)\]/', $markdown, $textMatches)) { // TODO improve bracket regex - $text = $textMatches[1]; - $offset = strlen($textMatches[0]); - $markdown = substr($markdown, $offset); - - $pattern = <<[^\s()]+)|(?R))*\) - | # else match a link with title - ^\(\s*(((?>[^\s()]+)|(?R))*)(\s+"(.*?)")?\s*\) - )/x -REGEXP; - if (preg_match($pattern, $markdown, $refMatches)) { - // inline link - return [ - $text, - isset($refMatches[2]) ? $refMatches[2] : '', // url - empty($refMatches[5]) ? null : $refMatches[5], // title - $offset + strlen($refMatches[0]), // offset - null, // reference key - ]; - } elseif (preg_match('/\((.*?)\)/', $markdown, $refMatches)) { - - // reference style link - if (empty($refMatches[1])) { - $key = strtolower($text); - } else { - $key = strtolower($refMatches[1]); - } - return [ - $text, - $refMatches[1], - $text, // title - $offset + strlen($refMatches[0]), // offset - null, - ]; - } - } - - return false; - } -} diff --git a/protected/humhub/libs/MarkdownPreview.php b/protected/humhub/libs/MarkdownPreview.php deleted file mode 100644 index 726e5bb11f..0000000000 --- a/protected/humhub/libs/MarkdownPreview.php +++ /dev/null @@ -1,165 +0,0 @@ -renderAbsy($block['content']) . "\n"; - } - - - /** - * Renders a headline - */ - protected function renderHeadline($block) - { - return $this->renderAbsy($block['content']) ."\n"; - } - - /** - * Parses a link indicated by `[`. - * @marker [ - */ - protected function parseLink($markdown) - { - if (!in_array('parseLink', array_slice($this->context, 1)) && ($parts = $this->parseLinkOrImage($markdown)) !== false) { - list($text, $url, $title, $offset, $key) = $parts; - return [ - [ - 'link', - 'text' => $this->parseInline($text), - 'url' => $url, - 'title' => $title, - 'refkey' => $key, - 'orig' => substr($markdown, 0, $offset), - ], - $offset - ]; - } else { - // remove all starting [ markers to avoid next one to be parsed as link - $result = '['; - $i = 1; - while (isset($markdown[$i]) && $markdown[$i] == '[') { - $result .= '['; - $i++; - } - return [['text', $result], $i]; - } - } - - /** - * - * @param type $block - * @marker ![ - */ - protected function parseImage($markdown) - { - if (($parts = $this->parseLinkOrImage(substr($markdown, 1))) !== false) { - list($text, $url, $title, $offset, $key) = $parts; - - return [ - [ - 'image', - 'text' => $text, - 'url' => $url, - 'title' => $title, - 'refkey' => $key, - 'orig' => substr($markdown, 0, $offset + 1), - ], - $offset + 1 - ]; - } else { - // remove all starting [ markers to avoid next one to be parsed as link - $result = '!'; - $i = 1; - while (isset($markdown[$i]) && $markdown[$i] == '[') { - $result .= '['; - $i++; - } - return [['text', $result], $i]; - } - } - - protected function parseLinkOrImage($markdown) - { - if (strpos($markdown, ']') !== false && preg_match('/\[((?>[^\]\[]+|(?R))*)\]/', $markdown, $textMatches)) { // TODO improve bracket regex - $text = $textMatches[1]; - $offset = strlen($textMatches[0]); - $markdown = substr($markdown, $offset); - - $pattern = <<[^\s()]+)|(?R))*\) - | # else match a link with title - ^\(\s*(((?>[^\s()]+)|(?R))*)(\s+"(.*?)")?\s*\) - )/x -REGEXP; - if (preg_match($pattern, $markdown, $refMatches)) { - // inline link - return [ - $text, - isset($refMatches[2]) ? $refMatches[2] : '', // url - empty($refMatches[5]) ? null : $refMatches[5], // title - $offset + strlen($refMatches[0]), // offset - null, // reference key - ]; - } elseif (preg_match('/^([ \n]?\[(.*?)\])?/s', $markdown, $refMatches)) { - // reference style link - if (empty($refMatches[2])) { - $key = strtolower($text); - } else { - $key = strtolower($refMatches[2]); - } - return [ - $text, - null, // url - null, // title - $offset + strlen($refMatches[0]), // offset - $key, - ]; - } - } - - return false; - } - - protected function renderLink($block) - { - $result = ''; - - if (isset($block['text']) && isset($block['text'][0]) && isset($block['text'][0][1])) { - $result = $block['text'][0][1]; - } - - if (!empty($result) && isset($block['url']) && strrpos($block['url'], 'mention:') === 0) { - $result = '@'.$result; - } - - return $result; - } - - protected function renderImage($block) - { - return "[" . $block['text'] . "]"; - } -} diff --git a/protected/humhub/modules/content/tests/codeception/unit/RichtextExtensionTest.php b/protected/humhub/modules/content/tests/codeception/unit/RichtextExtensionTest.php deleted file mode 100644 index 1ecfbbe125..0000000000 --- a/protected/humhub/modules/content/tests/codeception/unit/RichtextExtensionTest.php +++ /dev/null @@ -1,54 +0,0 @@ -](mention: ""). * * ### oembed - * * Enables scanning and replacement of pasted oembed links in form of link extensions [](oembed:url) * * ### placeholder - * * Text placeholder for the editor input * * ### strikethrough - * * Markdown strikethrough formatting. * * ### table - * * Simple Markdown table support. * * ### upload - * * File upload support. * * @author Julian Harrer @@ -150,11 +131,7 @@ class ProsemirrorRichText extends AbstractRichText */ public function run() { - if ($this->minimal) { - return static::convert($this->text, static::FORMAT_SHORTTEXT, ['maxLength' => $this->maxLength]); - } - - $output = $this->parseOutput(); + $output = $this->text; // E.g. when initializing empty editor if (empty($output)) { @@ -167,11 +144,6 @@ class ProsemirrorRichText extends AbstractRichText $output = $extension->onBeforeOutput($this, $output); } - // Can be removed in a future version, richtext should not be cut anymore, only text or shorttext version should be cut - if ($this->maxLength > 0) { - $output = Helpers::truncateText($output, $this->maxLength); - } - // Wrap encoded output in root div $this->content = Html::encode($output); $output = parent::run(); @@ -184,53 +156,4 @@ class ProsemirrorRichText extends AbstractRichText return trim($output); } - - /** - * Prior of 1.8 this function was used for preparing the richtext output. In 1.8 we richtext extensions should be - * used to manipulate the richtext output. - * - * @return string - * @deprecated since 1.8 use `RichTextExtension::onBeforeOutput()` to manipulate output - */ - protected function parseOutput() - { - return $this->text; - } - - /** - * Can be used to scan for link extensions of the form [](: "") in which the actual meaning - * of the placeholders is up to the extension itself. - * - * @param $text string rich text content to parse - * @param $extension string|null extension string if not given all extension types will be included - * @return array - * @deprecated since 1.8 use `ProsemirrorRichTextConverter::scanLinkExtension()` - */ - public static function scanLinkExtension($text, $extension = null) - { - $matches = []; - $result = RichTextLinkExtension::scanLinkExtension($text, $extension); - foreach ($result as $match) { - $matches[] = $match->match; - } - - return $matches; - } - - /** - * Can be used to scan and replace link extensions of the form [<text>](<extension>:<url> "<title>") in which the actual meaning - * of the placeholders is up to the extension itself. - * - * @param string|null $text string rich text content to parse - * @param string|null $extension extension string if not given all extension types will be included - * @param callable $callback - * @return mixed - * @deprecated since 1.8 use `ProsemirrorRichTextConverter::replaceLinkExtension()` - */ - public static function replaceLinkExtension(?string $text, ?string $extension, callable $callback) - { - return RichTextLinkExtension::replaceLinkExtension($text, $extension, function (RichTextLinkExtensionMatch $match) use ($callback) { - return $callback($match->match); - }); - } } diff --git a/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextConverter.php b/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextConverter.php index 62269b7c5d..972bdcec29 100644 --- a/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextConverter.php @@ -1,9 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext; - use humhub\modules\content\widgets\richtext\converter\RichTextToHtmlConverter; use humhub\modules\content\widgets\richtext\converter\RichTextToMarkdownConverter; use humhub\modules\content\widgets\richtext\converter\RichTextToPlainTextConverter; diff --git a/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextEditor.php b/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextEditor.php index 2ea2ccc487..f3e72d516f 100644 --- a/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextEditor.php +++ b/protected/humhub/modules/content/widgets/richtext/ProsemirrorRichTextEditor.php @@ -1,14 +1,7 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ - namespace humhub\modules\content\widgets\richtext; -use humhub\modules\content\assets\ProseMirrorRichTextAsset; use humhub\modules\file\widgets\UploadInput; /** diff --git a/protected/humhub/modules/content/widgets/richtext/RichText.php b/protected/humhub/modules/content/widgets/richtext/RichText.php index 4341b9fd3c..24cfd485f0 100644 --- a/protected/humhub/modules/content/widgets/richtext/RichText.php +++ b/protected/humhub/modules/content/widgets/richtext/RichText.php @@ -1,11 +1,5 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2018 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ - namespace humhub\modules\content\widgets\richtext; use Yii; diff --git a/protected/humhub/modules/content/widgets/richtext/RichTextField.php b/protected/humhub/modules/content/widgets/richtext/RichTextField.php index 8052391359..089f5db988 100644 --- a/protected/humhub/modules/content/widgets/richtext/RichTextField.php +++ b/protected/humhub/modules/content/widgets/richtext/RichTextField.php @@ -1,11 +1,5 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2018 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ - namespace humhub\modules\content\widgets\richtext; use Yii; diff --git a/protected/humhub/modules/content/widgets/richtext/converter/BaseRichTextConverter.php b/protected/humhub/modules/content/widgets/richtext/converter/BaseRichTextConverter.php index 8ce1175359..a2bad2e354 100644 --- a/protected/humhub/modules/content/widgets/richtext/converter/BaseRichTextConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/converter/BaseRichTextConverter.php @@ -1,13 +1,7 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2020 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ namespace humhub\modules\content\widgets\richtext\converter; - use cebe\markdown\GithubMarkdown; use humhub\components\ActiveRecord; use humhub\components\Event; @@ -19,22 +13,20 @@ use humhub\modules\content\widgets\richtext\extensions\link\RichTextLinkExtensio use humhub\modules\content\widgets\richtext\extensions\RichTextExtension; use humhub\modules\content\widgets\richtext\ProsemirrorRichText; use Yii; -use yii\base\InvalidArgumentException; /** - * This class serves as base class for richtext converters used to convert HumHub richtext to other formats. The base - * converter class extends GithubMarkdown markdown parser to support: + * This class serves as base class for richtext converters used to convert HumHub richtext to other formats. * - * - `onBeforeParse` and `onAfterparse` events + * The base converter class extends GithubMarkdown Markdown parser to support: + * - `onBeforeParse` and `onAfterParse` events * - new line by `\` * - registration of richtext extensions * - extended link/image regex e.g. for image size [Scaled Image](http://localhost/static/img/logo.png =150x) * - * The [[addExtension()]] function can be used to add additional richtext extensions. By default all extensions registered - * in [[ProsemirrorRichText::getExtensions()]] are available. + * The [[addExtension()]] function can be used to add additional richtext extensions. + * By default, all extensions registered in [[ProsemirrorRichText::getExtensions()]] are available. * - * > Note: The result of this parser will not be encoded, so do not directly add the result to a HTML view without - * encoding it. + * > Note: The result of this parser will not be encoded, so do not directly add the result to a HTML view without encoding it. * * @since 1.8 */ @@ -176,7 +168,7 @@ abstract class BaseRichTextConverter extends GithubMarkdown } $result = 'content_' . $content->content->id; - return $prefix ? $prefix.'_'.$result : $result; + return $prefix ? $prefix . '_' . $result : $result; } /** @@ -192,8 +184,8 @@ abstract class BaseRichTextConverter extends GithubMarkdown */ public static function buildCacheKeyForRecord(ActiveRecord $record, $prefix = null) { - $result = get_class($record).'_'.$record->getUniqueId(); - return $prefix ? $prefix.'_'.$result : $result; + $result = get_class($record) . '_' . $record->getUniqueId(); + return $prefix ? $prefix . '_' . $result : $result; } /** @@ -208,9 +200,10 @@ abstract class BaseRichTextConverter extends GithubMarkdown * Can be used to add additional richtext extensions * @param RichTextExtension $extension */ - public function addExtension(RichTextExtension $extension) { + public function addExtension(RichTextExtension $extension) + { $this->extensions[] = $extension; - if($extension instanceof RichTextLinkExtension) { + if ($extension instanceof RichTextLinkExtension) { $this->linkExtensions[] = $extension; } } @@ -224,17 +217,17 @@ abstract class BaseRichTextConverter extends GithubMarkdown $result = null; $cacheKey = $this->getOption(static::OPTION_CACHE_KEY, null); - if($cacheKey && isset(static::$cache[$cacheKey])) { + if ($cacheKey && isset(static::$cache[$cacheKey])) { $result = static::$cache[$cacheKey]; } - if($result === null) { + if ($result === null) { $result = $this->onBeforeParse($text); $result = parent::parse($result); // We cache the whole parser result, this way we can reuse the same result e.g. for different maxLength // or other post processes - if($cacheKey && count(static::$cache) < static::MAX_CACHE_ENTRIES) { + if ($cacheKey && count(static::$cache) < static::MAX_CACHE_ENTRIES) { static::$cache[$cacheKey] = $result; } } @@ -273,7 +266,7 @@ abstract class BaseRichTextConverter extends GithubMarkdown $text = $evt->result; // Remove leading new backslash new lines e.g. "Test\\\n" -> "Test" - $text = preg_replace('/\\\\(\n|\r){1,2}$/', '', $text); + $text = preg_replace('/\\\\(\n|\r){1,2}$/', '', $text); foreach ($this->extensions as $extension) { $text = $extension->onBeforeConvert($text, $this->format, $this->options); @@ -284,8 +277,8 @@ abstract class BaseRichTextConverter extends GithubMarkdown protected function renderAbsy($blocks) { - if(!empty($this->getExcludes())) { - $blocks = array_filter($blocks, function($block) { + if (!empty($this->getExcludes())) { + $blocks = array_filter($blocks, function ($block) { return !in_array($block[0], $this->getExcludes(), true); }); } @@ -308,12 +301,10 @@ abstract class BaseRichTextConverter extends GithubMarkdown * return $text; * } * ``` - * - * * @param $text * @return mixed|string */ - protected function onAfterParse($text) : string + protected function onAfterParse($text): string { $evt = new Event(['result' => $text]); Event::trigger($this, static::EVENT_AFTER_PARSE, $evt); @@ -373,7 +364,7 @@ REGEXP; return [ $text, isset($refMatches[2]) ? $refMatches[2] : '', // url - empty($refMatches[5]) ? null: $refMatches[5], // title + empty($refMatches[5]) ? null : $refMatches[5], // title $offset + strlen($refMatches[0]), // offset null, // reference key empty($refMatches[7]) ? null : $refMatches[7] // extension metadata @@ -409,24 +400,24 @@ REGEXP; */ protected function renderLink($block) { - return $this->renderLinkOrImage(new LinkParserBlock([ - 'block' => $block, - 'parsedText' => is_string($block['text']) ? $block['text'] : $this->renderAbsy($block['text']) - ])); + return $this->renderLinkOrImage(new LinkParserBlock([ + 'block' => $block, + 'parsedText' => is_string($block['text']) ? $block['text'] : $this->renderAbsy($block['text']) + ])); } protected function renderLinkOrImage(LinkParserBlock $linkBlock) { - if(!$linkBlock->getUrl()) { + if (!$linkBlock->getUrl()) { return $linkBlock->getParsedText(); } foreach ($this->linkExtensions as $linkExtension) { - if(in_array($linkExtension->key, $this->getExcludes())) { + if (in_array($linkExtension->key, $this->getExcludes())) { return ''; } - if($linkExtension->validateExtensionUrl($linkBlock->getUrl())) { + if ($linkExtension->validateExtensionUrl($linkBlock->getUrl())) { $linkExtension->onBeforeConvertLink($linkBlock); $linkBlock->toAbsoluteUrl(); return $this->renderLinkExtension($linkExtension, $linkBlock); @@ -435,7 +426,7 @@ REGEXP; $linkBlock->toAbsoluteUrl(); - if($linkBlock->isImage()) { + if ($linkBlock->isImage()) { return $this->renderPlainImage($linkBlock); } @@ -459,11 +450,11 @@ REGEXP; // Remove image alignment extension from image alt text $block['text'] = preg_replace('/>?<?$/', '', $text); - if($this->getOption(static::OPTION_IMAGE_AS_URL, false)) { + if ($this->getOption(static::OPTION_IMAGE_AS_URL, false)) { return Html::encode($block['url']); } - if($this->getOption(static::OPTION_IMAGE_AS_LINK, false)) { + if ($this->getOption(static::OPTION_IMAGE_AS_LINK, false)) { $text = empty($block['text']) ? $block['url'] : $block['text']; $linkBlock = $block; $linkBlock[0] = 'link'; @@ -483,12 +474,13 @@ REGEXP; * @param LinkParserBlock $linkBlock * @return string */ - protected function renderLinkExtension(RichTextLinkExtension $ext, LinkParserBlock $linkBlock) : string { - if($linkBlock->getResult()) { + protected function renderLinkExtension(RichTextLinkExtension $ext, LinkParserBlock $linkBlock): string + { + if ($linkBlock->getResult()) { return $linkBlock->getResult(); } - if($linkBlock->isImage()) { + if ($linkBlock->isImage()) { return $this->renderPlainImage($linkBlock); } @@ -499,7 +491,8 @@ REGEXP; * @param LinkParserBlock $linkBlock * @return string */ - protected function renderPlainLink(LinkParserBlock $linkBlock) : string { + protected function renderPlainLink(LinkParserBlock $linkBlock): string + { $block = $linkBlock->block; if (isset($block['refkey'])) { @@ -510,14 +503,14 @@ REGEXP; } } - if($this->getOption(static::OPTION_LINK_AS_TEXT, false)) { + if ($this->getOption(static::OPTION_LINK_AS_TEXT, false)) { return $this->renderAbsy($block['text']); } $target = Html::encode($this->getOption(static::OPTION_LINK_TARGET, '_blank')); $targetAttr = !$this->getOption(static::OPTION_PREV_LINK_TARGET, false) ? " target=\"$target\"" : ''; - return '<a href="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'. $targetAttr + return '<a href="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"' . $targetAttr . (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"') . '>' . $this->renderAbsy($block['text']) . '</a>'; } @@ -526,7 +519,8 @@ REGEXP; * @param LinkParserBlock $linkBlock * @return string */ - protected function renderPlainImage(LinkParserBlock $linkBlock) : string { + protected function renderPlainImage(LinkParserBlock $linkBlock): string + { return parent::renderImage($linkBlock->block); } @@ -559,4 +553,4 @@ REGEXP; return $result; } -} \ No newline at end of file +} diff --git a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToEmailHtmlConverter.php b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToEmailHtmlConverter.php index faa94320b8..1269c9bef4 100644 --- a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToEmailHtmlConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToEmailHtmlConverter.php @@ -1,11 +1,5 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2021 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ - namespace humhub\modules\content\widgets\richtext\converter; use humhub\libs\Html; @@ -15,8 +9,8 @@ use humhub\modules\file\models\File; use humhub\modules\user\models\User; /** - * This parser can be used to convert HumHub richtext directly to email html in order to view images from email inbox where - * user is not logged in so access is restricted. + * This parser can be used to convert HumHub richtext directly to email html in order to view images + * from email inbox where user is not logged in so access is restricted. * * @since 1.8.2 */ diff --git a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToHtmlConverter.php b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToHtmlConverter.php index 23f1f6ba28..0c2148b5ed 100644 --- a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToHtmlConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToHtmlConverter.php @@ -1,23 +1,10 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2020 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ - namespace humhub\modules\content\widgets\richtext\converter; - -use cebe\markdown\GithubMarkdown; -use cebe\markdown\inline\LinkTrait; use humhub\libs\Html; -use humhub\modules\content\widgets\richtext\extensions\link\RichTextLinkExtension; -use humhub\modules\content\widgets\richtext\extensions\link\RichTextLinkExtensionMatch; -use humhub\modules\content\widgets\richtext\extensions\RichTextExtension; use humhub\modules\content\widgets\richtext\ProsemirrorRichText; use yii\helpers\HtmlPurifier; -use yii\helpers\Url; /** * This parser can be used to convert HumHub richtext directly to html. Note, this parser will only output html supported @@ -28,7 +15,6 @@ use yii\helpers\Url; * The output parser output will be purified and can safely be used. * * Available options: - * * - `exclude`: Exclude certain blocks or extensions from being rendered * - `linkTarget`: Change link `target` (default `_blank`) * - `prevLinkTarget`: Removes `target` and `rel` attribute from all links @@ -41,7 +27,7 @@ class RichTextToHtmlConverter extends BaseRichTextConverter /** * @var string HtmlPurifier HTML.Doctype configuration */ - public $doctype = 'HTML 4.01 Transitional'; + public $doctype = 'HTML 4.01 Transitional'; /** * @var string HtmlPurifier URI.AllowedSchemes configuration @@ -76,11 +62,11 @@ class RichTextToHtmlConverter extends BaseRichTextConverter /** * @inheritDoc */ - protected function onAfterParse($text) : string + protected function onAfterParse($text): string { $text = parent::onAfterParse($text); - if(!$this->purify) { + if (!$this->purify) { return $text; } @@ -88,13 +74,13 @@ class RichTextToHtmlConverter extends BaseRichTextConverter // Make sure we use non xhtml tags, unfortunately HTML5 is not supported by html purifier $config->set('HTML.Doctype', $this->doctype); - if(!$this->getOption('prevLinkTarget', false)) { + if (!$this->getOption('prevLinkTarget', false)) { $config->set('HTML.Nofollow', true); } $config->set('HTML.Allowed', $this->htmlAllowed); $config->set('HTML.AllowedAttributes', $this->htmlAllowedAttributes); - $config->set('URI.AllowedSchemes',$this->allowedSchemes); + $config->set('URI.AllowedSchemes', $this->allowedSchemes); $htmlDefinition = $config->getHTMLDefinition(true); @@ -113,7 +99,6 @@ class RichTextToHtmlConverter extends BaseRichTextConverter } - /** * @param $block * @return string @@ -132,7 +117,7 @@ class RichTextToHtmlConverter extends BaseRichTextConverter protected function renderInlineHtml($block) { // We only support <br> tags - if($block[1] === '<br>' || $block[1] === '<br />') { + if ($block[1] === '<br>' || $block[1] === '<br />') { return '<br>'; } @@ -147,6 +132,6 @@ class RichTextToHtmlConverter extends BaseRichTextConverter protected function renderHtml($block) { // We do not support direct html in richtext markdown - return '<p>'.nl2br(Html::encode($this->br2nl($block['content']))).'</p>'; + return '<p>' . nl2br(Html::encode($this->br2nl($block['content']))) . '</p>'; } } diff --git a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToMarkdownConverter.php b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToMarkdownConverter.php index f6bd103868..c7dcdbe7aa 100644 --- a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToMarkdownConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToMarkdownConverter.php @@ -1,9 +1,4 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2020 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ namespace humhub\modules\content\widgets\richtext\converter; @@ -77,7 +72,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter /** * @inheritDoc */ - protected function onAfterParse($text) : string + protected function onAfterParse($text): string { return trim(parent::onAfterParse($text)); } @@ -85,33 +80,40 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter /** * html entity mark parser is disabled by removing marker in php doc */ - protected function parseEntity($text) { /* Not implemented */} + protected function parseEntity($text) + { /* Not implemented */ + } /** * `<` mark parser is disabled by removing marker in php doc */ - protected function parseLt($text) { /* Not implemented */} + protected function parseLt($text) + { /* Not implemented */ + } /** * `>` mark parser is disabled by removing marker in php doc */ - protected function parseGt($text) { /* Not implemented */} + protected function parseGt($text) + { /* Not implemented */ + } /** * @inheritDoc */ - protected function renderPlainLink(LinkParserBlock $linkBlock) : string + protected function renderPlainLink(LinkParserBlock $linkBlock): string { - return RichTextLinkExtension::buildLink($linkBlock->getParsedText(),$linkBlock->getUrl(), $linkBlock->getTitle()); + return RichTextLinkExtension::buildLink($linkBlock->getParsedText(), $linkBlock->getUrl(), $linkBlock->getTitle()); } /** * @inheritDoc */ - protected function renderPlainImage(LinkParserBlock $linkBlock) : string { + protected function renderPlainImage(LinkParserBlock $linkBlock): string + { $result = $this->renderPlainLink($linkBlock); - return $result[0] === '[' ? static::IMAGE_SUFFIX.$result : $result; + return $result[0] === '[' ? static::IMAGE_SUFFIX . $result : $result; } /** @@ -121,7 +123,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderEmail($block) { - return RichTextLinkExtension::buildLink($block[1], 'mailto:'.$block[1]); + return RichTextLinkExtension::buildLink($block[1], 'mailto:' . $block[1]); } /** @@ -155,14 +157,14 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderParagraph($block) { - return $this->renderAbsy($block['content'])."\n\n"; + return $this->renderAbsy($block['content']) . "\n\n"; } /** * Returns a plain text representation of a list block * @param $block * @return string - */ + */ protected function renderList($block) { $output = ''; @@ -170,16 +172,16 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter $level = $block['level'] ?? 0; foreach ($block['items'] as $item => $itemLines) { foreach ($itemLines as &$line) { - if($line[0] === 'list') { + if ($line[0] === 'list') { $line['level'] = $level + 1; } } - unset( $line ); + unset($line); - $output .= $level !== 0 ? "\n".str_repeat(' ', $level * 3) : ''; - $output .= $block['list'] === 'ol' ? (isset($block['origNums'][$item]) ? $block['origNums'][$item] : ++$count).'. ' : '- '; - $output .= $this->renderAbsy($itemLines). ($level === 0 ? "\n" : ''); + $output .= $level !== 0 ? "\n" . str_repeat(' ', $level * 3) : ''; + $output .= $block['list'] === 'ol' ? (isset($block['origNums'][$item]) ? $block['origNums'][$item] : ++$count) . '. ' : '- '; + $output .= $this->renderAbsy($itemLines) . ($level === 0 ? "\n" : ''); } return $output . ($level === 0 ? "\n" : ''); @@ -192,7 +194,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter protected function renderCode($block) { $lang = $block['language'] ?? ''; - return "```$lang\n".$block['content']."\n```\n\n"; + return "```$lang\n" . $block['content'] . "\n```\n\n"; } /** @@ -201,7 +203,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderQuote($block) { - return '> '.$this->renderAbsy($block['content']) . "\n\n"; + return '> ' . $this->renderAbsy($block['content']) . "\n\n"; } /** @@ -210,7 +212,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderHeadline($block) { - return str_repeat('#', $block['level']).' '.$this->renderAbsy($block['content'])."\n\n"; + return str_repeat('#', $block['level']) . ' ' . $this->renderAbsy($block['content']) . "\n\n"; } /** @@ -220,7 +222,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter protected function renderHtml($block) { // We do not strip_tags here, since the richtext does not support html and interprets html as normal text - return $this->br2nl($block['content']). "\n\n"; + return $this->br2nl($block['content']) . "\n\n"; } /** @@ -244,7 +246,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderStrike($block) { - return static::STRIKE_WRAPPER.$this->renderAbsy($block[1]).static::STRIKE_WRAPPER; + return static::STRIKE_WRAPPER . $this->renderAbsy($block[1]) . static::STRIKE_WRAPPER; } /** @@ -253,7 +255,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderStrong($block) { - return static::BOLD_WRAPPER.$this->renderAbsy($block[1]).static::BOLD_WRAPPER; + return static::BOLD_WRAPPER . $this->renderAbsy($block[1]) . static::BOLD_WRAPPER; } /** @@ -262,7 +264,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderEmph($block) { - return static::EMPHASIZE_WRAPPER.$this->renderAbsy($block[1]).static::EMPHASIZE_WRAPPER; + return static::EMPHASIZE_WRAPPER . $this->renderAbsy($block[1]) . static::EMPHASIZE_WRAPPER; } /** @@ -271,7 +273,7 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function renderInlineCode($block) { - return static::INLINE_CODE_WRAPPER.$block[1].static::INLINE_CODE_WRAPPER; + return static::INLINE_CODE_WRAPPER . $block[1] . static::INLINE_CODE_WRAPPER; } /** @@ -317,14 +319,14 @@ class RichTextToMarkdownConverter extends BaseRichTextConverter */ protected function identifyQuote($line) { - return $this->identifyQuote - ? parent::identifyQuote($line) - : false; + return $this->identifyQuote && parent::identifyQuote($line); } /** * Deactivated by removing marker */ - protected function parseTd($markdown) { /* Not implemented */ } + protected function parseTd($markdown) + { /* Not implemented */ + } } diff --git a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToPlainTextConverter.php b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToPlainTextConverter.php index c89e5a6665..b38fcb80d9 100644 --- a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToPlainTextConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToPlainTextConverter.php @@ -1,22 +1,11 @@ <?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2020 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ namespace humhub\modules\content\widgets\richtext\converter; - -use cebe\markdown\GithubMarkdown; -use cebe\markdown\inline\LinkTrait; use humhub\libs\Helpers; use humhub\modules\content\widgets\richtext\extensions\link\LinkParserBlock; use humhub\modules\content\widgets\richtext\extensions\link\RichTextLinkExtension; -use humhub\modules\content\widgets\richtext\extensions\link\RichTextLinkExtensionMatch; -use humhub\modules\content\widgets\richtext\extensions\RichTextExtension; use humhub\modules\content\widgets\richtext\ProsemirrorRichText; -use yii\helpers\Url; /** * This parser can be used to convert richtext or plain markdown to a plain text format used for example in @@ -91,9 +80,9 @@ class RichTextToPlainTextConverter extends RichTextToMarkdownConverter /** * @inheritDoc */ - protected function renderPlainLink(LinkParserBlock $linkBlock) : string + protected function renderPlainLink(LinkParserBlock $linkBlock): string { - if($linkBlock->getParsedText() === $linkBlock->getUrl()) { + if ($linkBlock->getParsedText() === $linkBlock->getUrl()) { return $linkBlock->getUrl(); } @@ -110,14 +99,14 @@ class RichTextToPlainTextConverter extends RichTextToMarkdownConverter $maxLength = $this->getOption(static::OPTION_MAX_LENGTH, 0); // In case of a given cache key, we need to make sure to parse and cache the full text - if(!$maxLength || $this->getOption(static::OPTION_CACHE_KEY, null)) { + if (!$maxLength || $this->getOption(static::OPTION_CACHE_KEY, null)) { return $paragraph; } foreach ($paragraph as $inline) { - if(isset($inline[0], $inline[1]) && $inline[0] === 'text') { + if (isset($inline[0], $inline[1]) && $inline[0] === 'text') { $this->textCount += mb_strlen($inline[1]); - if($this->textCount > $maxLength) { + if ($this->textCount > $maxLength) { $this->skipBlocks = true; } } @@ -131,7 +120,7 @@ class RichTextToPlainTextConverter extends RichTextToMarkdownConverter */ protected function parseBlock($lines, $current) { - if($this->skipBlocks) { + if ($this->skipBlocks) { return [false, count($lines)]; } @@ -146,7 +135,8 @@ class RichTextToPlainTextConverter extends RichTextToMarkdownConverter /** * @inheritDoc */ - protected function renderPlainImage(LinkParserBlock $linkBlock) : string { + protected function renderPlainImage(LinkParserBlock $linkBlock): string + { return $this->renderPlainLink($linkBlock); } @@ -186,12 +176,12 @@ class RichTextToPlainTextConverter extends RichTextToMarkdownConverter /** * @inheritDoc */ - protected function onAfterParse($text) : string + protected function onAfterParse($text): string { $result = parent::onAfterParse($text); $maxLength = $this->getOption(static::OPTION_MAX_LENGTH, 0); - if($maxLength > 0) { + if ($maxLength > 0) { $result = Helpers::truncateText($result, $maxLength); } diff --git a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToShortTextConverter.php b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToShortTextConverter.php index 77ba0d043f..54c2d27fd0 100644 --- a/protected/humhub/modules/content/widgets/richtext/converter/RichTextToShortTextConverter.php +++ b/protected/humhub/modules/content/widgets/richtext/converter/RichTextToShortTextConverter.php @@ -1,10 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext\converter; - -use humhub\libs\Helpers; use humhub\libs\Html; use humhub\modules\content\widgets\richtext\extensions\link\LinkParserBlock; use humhub\modules\content\widgets\richtext\extensions\link\RichTextLinkExtension; @@ -47,7 +44,7 @@ class RichTextToShortTextConverter extends RichTextToPlainTextConverter /** * @inheritDoc */ - protected function renderPlainLink(LinkParserBlock $linkBlock) : string + protected function renderPlainLink(LinkParserBlock $linkBlock): string { return $linkBlock->getParsedText(); } @@ -74,7 +71,7 @@ class RichTextToShortTextConverter extends RichTextToPlainTextConverter */ protected function renderCode($block) { - return Yii::t('ContentModule.richtexteditor', '[Code Block]')."\n\n"; + return Yii::t('ContentModule.richtexteditor', '[Code Block]') . "\n\n"; } /** @@ -82,7 +79,7 @@ class RichTextToShortTextConverter extends RichTextToPlainTextConverter */ protected function renderTable($block) { - return Yii::t('ContentModule.richtexteditor', '[Table]')."\n\n"; + return Yii::t('ContentModule.richtexteditor', '[Table]') . "\n\n"; } /** @@ -90,7 +87,7 @@ class RichTextToShortTextConverter extends RichTextToPlainTextConverter */ protected function renderHeadline($block) { - return $this->renderAbsy($block['content'])."\n\n"; + return $this->renderAbsy($block['content']) . "\n\n"; } /** @@ -99,13 +96,13 @@ class RichTextToShortTextConverter extends RichTextToPlainTextConverter */ protected function renderImage($block) { - return Yii::t('ContentModule.richtexteditor', '[Image]')."\n\n"; + return Yii::t('ContentModule.richtexteditor', '[Image]') . "\n\n"; } /** * @inheritDoc */ - protected function renderPlainImage(LinkParserBlock $linkBlock) : string + protected function renderPlainImage(LinkParserBlock $linkBlock): string { $url = $linkBlock->getUrl(); return RichTextLinkExtension::validateNonExtensionUrl($url) ? $url : ''; @@ -114,18 +111,18 @@ class RichTextToShortTextConverter extends RichTextToPlainTextConverter /** * @inheritDoc */ - protected function onAfterParse($text) : string + protected function onAfterParse($text): string { $result = $text; - if(!$this->getOption(static::OPTION_PRESERVE_SPACES, false)) { - $result = trim(preg_replace('/\s+/', ' ', $result)); + if (!$this->getOption(static::OPTION_PRESERVE_SPACES, false)) { + $result = trim(preg_replace('/\s+/', ' ', $result)); } $result = parent::onAfterParse($result); $result = Html::encode($result); - if($this->getOption(static::OPTION_NL2BR, false)) { + if ($this->getOption(static::OPTION_NL2BR, false)) { $result = nl2br($result, false); } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextCompatibilityExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextCompatibilityExtension.php index 1746a927f6..34e01295de 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextCompatibilityExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextCompatibilityExtension.php @@ -1,9 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions; - use humhub\components\ActiveRecord; use humhub\models\UrlOembed; use humhub\modules\content\Module; @@ -20,8 +18,6 @@ use yii\helpers\Html; * The legacy format uses a different syntax for emoji, mentioning, oembed. * * This conversion can be deactivated either by module configuration or by module db setting `richtextCompatMode`. - * - * @package humhub\modules\content\widgets\richtext\extensions */ class RichTextCompatibilityExtension extends Model implements RichTextExtension { @@ -35,7 +31,7 @@ class RichTextCompatibilityExtension extends Model implements RichTextExtension */ public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string { - if(!$this->isCompatibilityMode()) { + if (!$this->isCompatibilityMode()) { return $output; } @@ -51,7 +47,7 @@ class RichTextCompatibilityExtension extends Model implements RichTextExtension * @param string $show show smilies or remove it (for activities and notifications) * @return string */ - public static function translateEmojis(string $text) : string + public static function translateEmojis(string $text): string { $emojis = [ "Relaxed", "Yum", "Relieved", "Hearteyes", "Cool", "Smirk", @@ -111,7 +107,7 @@ class RichTextCompatibilityExtension extends Model implements RichTextExtension * @param $text * @return mixed */ - private static function translateLinks(string $text) : string + private static function translateLinks(string $text): string { return preg_replace_callback('/(?<=^|\s)(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s\]\)\\"\'\<]{2,})(?=$|\s)/', function ($hit) { $url = $hit[0]; @@ -125,7 +121,7 @@ class RichTextCompatibilityExtension extends Model implements RichTextExtension * @param $text * @return mixed */ - private static function translateMentionings(string $text) : string + private static function translateMentionings(string $text): string { return preg_replace_callback('@\@\-([us])([\w\-]*?)($|[\.,:;\'"!\?\s])@', function ($hit) { if ($hit[1] == 'u') { @@ -152,7 +148,7 @@ class RichTextCompatibilityExtension extends Model implements RichTextExtension */ private function isCompatibilityMode() { - /* @var $module Module */ + /* @var $module Module */ $module = Yii::$app->getModule('content'); return $module->richtextCompatMode && $module->settings->get(static::DB_SETTING_KEY, 1); } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextContentExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextContentExtension.php index 7de77c2194..e0b2290a22 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextContentExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextContentExtension.php @@ -1,9 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions; - use humhub\modules\content\widgets\richtext\ProsemirrorRichText; use yii\base\Model; use humhub\components\ActiveRecord; @@ -30,13 +28,13 @@ abstract class RichTextContentExtension extends Model implements RichTextExtensi * @param RichTextExtensionMatch $match * @return string */ - public abstract function initMatch(array $match) : RichTextExtensionMatch; + public abstract function initMatch(array $match): RichTextExtensionMatch; /** * @param array $match * @return string */ - public abstract function getRegex() : string; + public abstract function getRegex(): string; /** * @param $text @@ -51,7 +49,7 @@ abstract class RichTextContentExtension extends Model implements RichTextExtensi * @param $text * @return string */ - public static function replace($text, callable $callback) : string + public static function replace($text, callable $callback): string { return static::instance()->replaceExtension($text, $callback); } @@ -78,7 +76,7 @@ abstract class RichTextContentExtension extends Model implements RichTextExtensi */ protected function replaceExtension($text, callable $callback) { - return preg_replace_callback($this->getRegex(), function($match) use ($callback) { + return preg_replace_callback($this->getRegex(), function ($match) use ($callback) { return $callback($this->initMatch($match)); }, $text); } @@ -88,11 +86,13 @@ abstract class RichTextContentExtension extends Model implements RichTextExtensi return $text; } - public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string { + public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string + { return $output; } - public function onAfterOutput(ProsemirrorRichText $richtext, string $output): string { + public function onAfterOutput(ProsemirrorRichText $richtext, string $output): string + { return $output; } } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtension.php index d080ce6a13..0854b75a01 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtension.php @@ -1,9 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions; - use humhub\modules\content\widgets\richtext\ProsemirrorRichText; use humhub\components\ActiveRecord; diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtensionMatch.php b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtensionMatch.php index 9281780629..f4e32400f4 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtensionMatch.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/RichTextExtensionMatch.php @@ -1,16 +1,12 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions; - use yii\base\Model; /** * A RichTextExtensionMatch wraps regex results of richtext extensions and provides helper functions to access * data of a match. - * - * @package humhub\modules\content\widgets\richtext\extensions */ abstract class RichTextExtensionMatch extends Model { @@ -23,26 +19,26 @@ abstract class RichTextExtensionMatch extends Model * Returns the full match string * @return string */ - public abstract function getFull() : string; + public abstract function getFull(): string; /** * Returns the extension key * @return string */ - public abstract function getExtensionKey() : string; + public abstract function getExtensionKey(): string; /** * Returns the id of this extension match, the id usually identifies this instance e.g. an url * @return string */ - public abstract function getExtensionId() : ?string; + public abstract function getExtensionId(): ?string; /** * Returns the value of a given match index or null * @param $index * @return string|null */ - public function getByIndex(int $index) : ?string + public function getByIndex(int $index): ?string { return $this->match[$index] ?? null; } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtension.php index 396e07f231..2210c03e8c 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtension.php @@ -11,12 +11,9 @@ use humhub\components\ActiveRecord; /** * The emoji richtext extension is responsible for replacing richtext emoji syntax like :smile: to utf8 characters when * converting a richtext to other formats. - * - * @package humhub\modules\content\widgets\richtext\extensions\emoji */ class RichTextEmojiExtension extends RichTextContentExtension { - /** * @inheritdoc */ @@ -25,7 +22,8 @@ class RichTextEmojiExtension extends RichTextContentExtension /** * @inheritdoc */ - public function onBeforeConvert(string $text, string $format, array $options = []) : string { + public function onBeforeConvert(string $text, string $format, array $options = []): string + { return static::convertEmojiToUtf8($text); } @@ -33,11 +31,11 @@ class RichTextEmojiExtension extends RichTextContentExtension * @param $text * @return string */ - public static function convertEmojiToUtf8($text) : string + public static function convertEmojiToUtf8($text): string { // Note the ; was used in the legacy editor - return static::replace($text, function(RichTextEmojiExtensionMatch $match) { - if(!empty($match->getEmojiName())) { + return static::replace($text, function (RichTextEmojiExtensionMatch $match) { + if (!empty($match->getEmojiName())) { $name = $match->getEmojiName(); return array_key_exists(strtolower($name), EmojiMap::MAP) ? EmojiMap::MAP[strtolower($name)] diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtensionMatch.php b/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtensionMatch.php index 7a81da5e0a..9a6574306f 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtensionMatch.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/emoji/RichTextEmojiExtensionMatch.php @@ -1,22 +1,16 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions\emoji; - -use humhub\libs\EmojiMap; use humhub\modules\content\widgets\richtext\extensions\RichTextExtensionMatch; /** * Richtext emoji extension match contains the result of the following emoji format: * * :<emojiName>: - * - * @package humhub\modules\content\widgets\richtext\extensions\emoji */ class RichTextEmojiExtensionMatch extends RichTextExtensionMatch { - /** * Returns the full match string * @return string diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/file/FileExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/file/FileExtension.php index a82d6b96d1..4e55ffce5a 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/file/FileExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/file/FileExtension.php @@ -16,7 +16,6 @@ use yii\web\UploadedFile; * This LinkExtension is used to represent mentionings in the richtext as: * * [<name>](mention:<guid> "<url>") - * */ class FileExtension extends RichTextLinkExtension { @@ -25,13 +24,13 @@ class FileExtension extends RichTextLinkExtension /** * @inheritDoc */ - public function onBeforeConvertLink(LinkParserBlock $linkBlock) : void + public function onBeforeConvertLink(LinkParserBlock $linkBlock): void { $guid = $this->cutExtensionKeyFromUrl($linkBlock->getUrl()); $file = File::findOne(['guid' => $guid]); - if(!$file) { + if (!$file) { $linkBlock->setResult($linkBlock->getParsedText()); return; } @@ -39,15 +38,15 @@ class FileExtension extends RichTextLinkExtension $linkBlock->setBlock($linkBlock->getParsedText(), $file->getUrl(), null, $file->id); } - public static function buildFileLink(File $file) : string + public static function buildFileLink(File $file): string { - return static::buildLink($file->file_name, 'file-guid:'.$file->guid, $file->getUrl([], true)); + return static::buildLink($file->file_name, 'file-guid:' . $file->guid, $file->getUrl([], true)); } - public static function buildFileNotFound($name, $guid) : string + public static function buildFileNotFound($name, $guid): string { - return '['.$name.'](mention:'.$guid.' "#")'; + return '[' . $name . '](mention:' . $guid . ' "#")'; } public function onBeforeConvert(string $text, string $format, array $options = []): string @@ -57,24 +56,24 @@ class FileExtension extends RichTextLinkExtension public function onPostProcess(string $text, ActiveRecord $record, ?string $attribute, array &$result): string { - if($record->isNewRecord) { + if ($record->isNewRecord) { // We can't attach files to unpersisted records return $text; } $result[$this->key] = []; foreach ($this->scanExtension($text) as $match) { - if($match->getExtensionId()) { - if($this->attach($record, $match->getExtensionId())) { + if ($match->getExtensionId()) { + if ($this->attach($record, $match->getExtensionId())) { $result[$this->key][] = $match->getExtensionId(); } } } - $text = static::replaceLinkExtension($text, 'data', function(RichTextLinkExtensionMatch $match) use($record, &$result) { - if($match->getExtensionId()) { + $text = static::replaceLinkExtension($text, 'data', function (RichTextLinkExtensionMatch $match) use ($record, &$result) { + if ($match->getExtensionId()) { $file = $this->parseBase64Data($match->getExtensionId(), $record); - if($file && $file->guid) { + if ($file && $file->guid) { $result[$this->key][] = $file->guid; //return '['.$file->file_name.'](file-guid:'.$file->guid.' "'.$file->file_name.'"'.(isset($match[4]) ? $match[4] : '').')'; return $this->buildExtensionLink($file->file_name, $file->guid, $file->file_name, $match->getAddition()); @@ -95,7 +94,7 @@ class FileExtension extends RichTextLinkExtension try { $file = File::findOne(['guid' => $fileGuid]); - if(!$file) { + if (!$file) { return false; } @@ -118,26 +117,26 @@ class FileExtension extends RichTextLinkExtension try { preg_match('/^([-\w.]+\/[-\w.+]+);([^,]+),([a-zA-Z0-9\/\r\n+]*={0,2})$/s', $dataStr, $matches); - if(!isset($matches[1]) || !isset($matches[3])) { + if (!isset($matches[1]) || !isset($matches[3])) { return false; } $mime = $matches[1]; $extensions = FileHelper::getExtensionsByMimeType($mime); - if(empty($extensions)) { + if (empty($extensions)) { return false; } $extension = end($extensions); $data = $this->decode_base64($matches[3]); - if(!$data) { + if (!$data) { return false; } $uploadedFile = new UploadedFile([ - 'name' => 'someFile.'.$extension, + 'name' => 'someFile.' . $extension, 'tempName' => $this->createTmpFile($data), 'size' => strlen($data), 'type' => $mime, @@ -146,11 +145,11 @@ class FileExtension extends RichTextLinkExtension $fileUpload = new FileUpload(['show_in_stream' => 0]); $fileUpload->setUploadedFile($uploadedFile); - if(!$fileUpload->save()) { + if (!$fileUpload->save()) { return false; } - $fileUpload->updateAttributes(['file_name' => $fileUpload->guid.'.'.$extension]); + $fileUpload->updateAttributes(['file_name' => $fileUpload->guid . '.' . $extension]); // Since the file is not a real upload, FileUpload won't set the content automatically $fileUpload->setStoredFileContent($data); @@ -174,7 +173,7 @@ class FileExtension extends RichTextLinkExtension // Decode the string in strict mode and check the results $decoded = base64_decode($s, true); - if($decoded === false) { + if ($decoded === false) { return false; } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/link/LinkParserBlock.php b/protected/humhub/modules/content/widgets/richtext/extensions/link/LinkParserBlock.php index f5a545a518..2ff7dcb633 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/link/LinkParserBlock.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/link/LinkParserBlock.php @@ -1,9 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions\link; - use yii\base\Model; use yii\helpers\Url; @@ -43,21 +41,21 @@ class LinkParserBlock extends Model */ public $result; - public function getMarkdown() : ?string + public function getMarkdown(): ?string { return $this->block[static::BLOCK_KEY_MD] ?? null; } - public function getUrl() : ?string + public function getUrl(): ?string { return $this->block[static::BLOCK_KEY_URL] ?? null; } - public function toAbsoluteUrl() : void + public function toAbsoluteUrl(): void { $url = $this->getUrl(); - if($url && $url[0] === '/') { - $url = Url::base(true).$url; + if ($url && $url[0] === '/') { + $url = Url::base(true) . $url; } $this->setUrl($url); @@ -68,7 +66,7 @@ class LinkParserBlock extends Model $this->block[static::BLOCK_KEY_URL] = $url; } - public function getText() : ?array + public function getText(): ?array { return $this->block[static::BLOCK_KEY_TEXT] ?? null; } @@ -79,7 +77,7 @@ class LinkParserBlock extends Model $this->setParsedText($text); } - public function getTitle() : ?string + public function getTitle(): ?string { return $this->block[static::BLOCK_KEY_TITLE] ?? null; } @@ -89,7 +87,7 @@ class LinkParserBlock extends Model $this->block[static::BLOCK_KEY_TITLE] = $title; } - public function getFileId() : ?string + public function getFileId(): ?string { return $this->block[static::BLOCK_KEY_FILE_ID] ?? null; } @@ -144,11 +142,11 @@ class LinkParserBlock extends Model private function textToBlockFormat(string $text) { - if($this->isImage()) { + if ($this->isImage()) { return $text; } - if(!$text) { + if (!$text) { $text = ''; } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtension.php index 8170395e65..6c5ee74a2a 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtension.php @@ -40,7 +40,7 @@ class RichTextLinkExtension extends RichTextContentExtension * @param RichTextExtensionMatch $match * @return string */ - public function initMatch(array $match) : RichTextExtensionMatch + public function initMatch(array $match): RichTextExtensionMatch { return new RichTextLinkExtensionMatch(['match' => $match]); } @@ -48,18 +48,18 @@ class RichTextLinkExtension extends RichTextContentExtension public static function convertToPlainText($text, $url) { - if(!static::validateNonExtensionUrl($url)) { + if (!static::validateNonExtensionUrl($url)) { return $text; } - return trim($text).'('.$url.')'; + return trim($text) . '(' . $url . ')'; } public static function validateNonExtensionUrl($url) { $protocols = ['http', 'https', 'mailto', '#', 'ftp', 'ftps', '/']; foreach ($protocols as $protocol) { - if(strpos($url, $protocol . ':') === 0) { + if (strpos($url, $protocol . ':') === 0) { return true; } } @@ -76,36 +76,36 @@ class RichTextLinkExtension extends RichTextContentExtension return static::getLinkExtensionPattern($this->key); } - public function validateExtensionUrl(string $url) : bool + public function validateExtensionUrl(string $url): bool { return strpos($url, $this->key . ':') === 0; } - public static function buildLink(string $text, string $url, string $title = null) : string + public static function buildLink(string $text, string $url, string $title = null): string { - if(!$title) { - return '['.$text.']('.$url.')'; + if (!$title) { + return '[' . $text . '](' . $url . ')'; } - return '['.$text.']('.$url.' "'.$title.'")'; + return '[' . $text . '](' . $url . ' "' . $title . '")'; } - public static function buildExtensionLink(string $text, string $extensionId, string $title = null, string $addition = '') : string + public static function buildExtensionLink(string $text, string $extensionId, string $title = null, string $addition = ''): string { - if(!empty($addition)) { - $addition = ' '.$addition; + if (!empty($addition)) { + $addition = ' ' . $addition; } - if(!$title) { - return '['.$text.']('.static::instance()->key.':'.$extensionId.$addition.')'; + if (!$title) { + return '[' . $text . '](' . static::instance()->key . ':' . $extensionId . $addition . ')'; } - return '['.$text.']('.static::instance()->key.':'.$extensionId.' "'.$title.'"'.$addition.')'; + return '[' . $text . '](' . static::instance()->key . ':' . $extensionId . ' "' . $title . '"' . $addition . ')'; } - public function cutExtensionKeyFromUrl(string $url) : string + public function cutExtensionKeyFromUrl(string $url): string { - if(!$this->validateExtensionUrl($url)) { + if (!$this->validateExtensionUrl($url)) { return $url; } @@ -116,14 +116,14 @@ class RichTextLinkExtension extends RichTextContentExtension * @param string $extension the extension to parse, if not set all extensions are included * @return string the regex pattern for a given extension or all extension if no specific extension string is given */ - public static function getLinkExtensionPattern($extension = '[a-zA-Z-_]+') : string + public static function getLinkExtensionPattern($extension = '[a-zA-Z-_]+'): string { - if($extension === null) { - $extension = '[a-zA-Z-_]+'; + if ($extension === null) { + $extension = '[a-zA-Z-_]+'; } // [<text>](<extension>:<id> "<title>" <addition> ) - return '/(?<!\\\\)!?\[([^\]]*)\]\(('.$extension.'):{1}([^\)\s]*)(?:\s)?(?:"([^"]*)")?(?:\s)?([^\)]*)\)/is'; + return '/(?<!\\\\)!?\[([^\]]*)\]\((' . $extension . '):{1}([^\)\s]*)(?:\s)?(?:"([^"]*)")?(?:\s)?([^\)]*)\)/is'; } public function onBeforeConvert(string $text, string $format, array $options = []): string @@ -136,11 +136,13 @@ class RichTextLinkExtension extends RichTextContentExtension // TODO: Implement onBeforeConvertLink() method. } - public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string { + public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string + { return $output; } - public function onAfterOutput(ProsemirrorRichText $richtext, string $output): string { + public function onAfterOutput(ProsemirrorRichText $richtext, string $output): string + { return $output; } } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtensionMatch.php b/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtensionMatch.php index cc19e83da0..41323d624b 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtensionMatch.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/link/RichTextLinkExtensionMatch.php @@ -1,9 +1,7 @@ <?php - namespace humhub\modules\content\widgets\richtext\extensions\link; - use humhub\modules\content\widgets\richtext\extensions\RichTextExtensionMatch; /** @@ -30,47 +28,47 @@ class RichTextLinkExtensionMatch extends RichTextExtensionMatch /** * @return string */ - public function getFull() : string + public function getFull(): string { return $this->getByIndex(static::INDEX_FULL); } - public function getText() : string + public function getText(): string { return $this->getByIndex(static::INDEX_CONTENT); } - public function getExtensionKey() : string + public function getExtensionKey(): string { return $this->getByIndex(static::INDEX_EXTENSION_KEY); } - public function getExtensionId() : string + public function getExtensionId(): string { return $this->getByIndex(static::INDEX_EXTENSION_ID); } - public function getExtensionUrl() : string + public function getExtensionUrl(): string { return $this->getExtensionKey() . ':' . $this->getExtensionId(); } - public function getTitle() : string + public function getTitle(): string { return $this->getByIndex(static::INDEX_TITLE); } - public function getAddition() : ?string + public function getAddition(): ?string { return $this->getByIndex(static::INDEX_ADDITION); } - public function getByIndex(int $index) : string + public function getByIndex(int $index): string { return $this->match[$index] ?? ''; } - public function isImage() : bool + public function isImage(): bool { return $this->getFull()[0] === '!'; } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/mentioning/MentioningExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/mentioning/MentioningExtension.php index ab9993dd90..8b76e6479e 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/mentioning/MentioningExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/mentioning/MentioningExtension.php @@ -19,18 +19,17 @@ use humhub\components\ActiveRecord; * This LinkExtension is used to represent mentionings in the richtext as: * * [<name>](mention:<guid> "<url>") - * */ class MentioningExtension extends RichTextLinkExtension { public $key = 'mention'; - public function onBeforeOutput(ProsemirrorRichText $richtext, string $output) : string + public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string { - return static::replace($output, function(RichTextLinkExtensionMatch $match) use ($richtext) { + return static::replace($output, function (RichTextLinkExtensionMatch $match) use ($richtext) { $contentContainer = ContentContainer::findOne(['guid' => $match->getExtensionId()]); - if(!$contentContainer || !$contentContainer->getPolymorphicRelation()) { + if (!$contentContainer || !$contentContainer->getPolymorphicRelation()) { // If no user or space was found we leave out the url in the non edit mode. return $richtext->edit ? static::buildExtensionLink($match->getText(), $match->getText(), $match->getTitle()) @@ -39,13 +38,13 @@ class MentioningExtension extends RichTextLinkExtension $container = $contentContainer->getPolymorphicRelation(); - if($container instanceof User) { + if ($container instanceof User) { return $container->isActive() ? static::buildMentioning($container) : static::buildMentioningNotFound($match->getText(), $match->getExtensionId()); } - if($container instanceof Space) { + if ($container instanceof Space) { return self::buildMentioning($container); } @@ -56,41 +55,41 @@ class MentioningExtension extends RichTextLinkExtension /** * @inheritDoc */ - public function onBeforeConvertLink(LinkParserBlock $linkBlock) : void + public function onBeforeConvertLink(LinkParserBlock $linkBlock): void { $guid = $this->cutExtensionKeyFromUrl($linkBlock->getUrl()); $container = $this->findContainer($guid); - if(!$container || ($container instanceof User && !$container->isActive())) { - $linkBlock->setResult('@'.$linkBlock->getParsedText()); + if (!$container || ($container instanceof User && !$container->isActive())) { + $linkBlock->setResult('@' . $linkBlock->getParsedText()); return; } - $linkBlock->setBlock('@'.$container->getDisplayName(), $container->createUrl(null, [], true)); + $linkBlock->setBlock('@' . $container->getDisplayName(), $container->createUrl(null, [], true)); } - private function findContainer($guid) : ?ContentContainerActiveRecord + private function findContainer($guid): ?ContentContainerActiveRecord { $result = User::findOne(['guid' => $guid]); - if(!$result) { + if (!$result) { $result = Space::findOne(['guid' => $guid]); } return $result; } - public static function buildMentioning(ContentContainerActiveRecord $container, $urlScheme = false) : string + public static function buildMentioning(ContentContainerActiveRecord $container, $urlScheme = false): string { - if($container instanceof User && !$container->isActive()) { + if ($container instanceof User && !$container->isActive()) { return static::buildMentioningNotFound($container->getDisplayName(), $container->guid); } - return static::buildLink($container->getDisplayName(), 'mention:'.$container->guid, $container->getUrl($urlScheme)); + return static::buildLink($container->getDisplayName(), 'mention:' . $container->guid, $container->getUrl($urlScheme)); } - private static function buildMentioningNotFound($name, $guid) : string + private static function buildMentioningNotFound($name, $guid): string { return static::buildExtensionLink($name, $guid); } @@ -100,14 +99,14 @@ class MentioningExtension extends RichTextLinkExtension $result[$this->key] = []; // We currently only support mentionings in content or content addon records - if(!($record instanceof ContentActiveRecord) && !($record instanceof ContentAddonActiveRecord)) { + if (!($record instanceof ContentActiveRecord) && !($record instanceof ContentAddonActiveRecord)) { return $text; } foreach ($this->scanExtension($text) as $match) { - if($match->getExtensionId()) { + if ($match->getExtensionId()) { $mention = Mentioning::mention($match->getExtensionId(), $record); - if(!empty($mention)) { + if (!empty($mention)) { $result[$this->key][] = $mention[0]; } } diff --git a/protected/humhub/modules/content/widgets/richtext/extensions/oembed/OembedExtension.php b/protected/humhub/modules/content/widgets/richtext/extensions/oembed/OembedExtension.php index 10176b9a39..250befa5cf 100644 --- a/protected/humhub/modules/content/widgets/richtext/extensions/oembed/OembedExtension.php +++ b/protected/humhub/modules/content/widgets/richtext/extensions/oembed/OembedExtension.php @@ -13,7 +13,6 @@ use yii\helpers\Html; * This LinkExtension is used to represent mentionings in the richtext as: * * [<name>](mention:<guid> "<url>") - * */ class OembedExtension extends RichTextLinkExtension { @@ -29,24 +28,26 @@ class OembedExtension extends RichTextLinkExtension */ private $oembeds = []; - public function onBeforeConvertLink(LinkParserBlock $linkBlock) : void + public function onBeforeConvertLink(LinkParserBlock $linkBlock): void { $linkBlock->setUrl($this->cutExtensionKeyFromUrl($linkBlock->getUrl())); } - public function onBeforeOutput(ProsemirrorRichText $richtext, string $output) : string { + public function onBeforeOutput(ProsemirrorRichText $richtext, string $output): string + { $this->oembeds = static::parseOembeds($output, static::$maxOembed); return $output; } - public function onAfterOutput(ProsemirrorRichText $richtext, string $output) : string { + public function onAfterOutput(ProsemirrorRichText $richtext, string $output): string + { return $output . $this->buildOembedOutput(); } /** * @return string html extension holding the actual oembed dom nodes which will be embedded into the rich text */ - private function buildOembedOutput() : string + private function buildOembedOutput(): string { $result = ''; foreach ($this->oembeds as $url => $oembed) { @@ -56,10 +57,9 @@ class OembedExtension extends RichTextLinkExtension return Html::tag('div', $result, ['class' => 'richtext-oembed-container', 'style' => 'display:none']); } - public static function builOembed($url) : string + public static function builOembed($url): string { - - return static::buildLink($url, 'oembed:'.$url); + return static::buildLink($url, 'oembed:' . $url); } public static function parseOembeds($text, $max = 100) @@ -67,13 +67,13 @@ class OembedExtension extends RichTextLinkExtension $result = []; $oembedCount = 0; foreach (static::scanLinkExtension($text) as $match) { - if($oembedCount === $max) { + if ($oembedCount === $max) { break; } - if(!empty($match->getExtensionId())) { - $oembedPreview = UrlOembed::getOEmbed($match->getExtensionId()); - if(!empty($oembedPreview)) { + if (!empty($match->getExtensionId())) { + $oembedPreview = UrlOembed::getOEmbed($match->getExtensionId()); + if (!empty($oembedPreview)) { $oembedCount++; $result[$match->getExtensionId()] = $oembedPreview; } @@ -83,9 +83,9 @@ class OembedExtension extends RichTextLinkExtension return $result; } - public static function buildOembedNotFound($url) : string + public static function buildOembedNotFound($url): string { - return '['.$url.']('.$url.')'; + return '[' . $url . '](' . $url . ')'; } /** @@ -102,7 +102,7 @@ class OembedExtension extends RichTextLinkExtension { $result[$this->key] = []; foreach ($this->scanExtension($text) as $match) { - if($match->getExtensionId() && UrlOembed::hasOEmbedSupport($match->getExtensionId())) { + if ($match->getExtensionId() && UrlOembed::hasOEmbedSupport($match->getExtensionId())) { UrlOembed::preload($match->getExtensionId()); $result[$this->key][] = $match->getExtensionId(); } diff --git a/protected/humhub/modules/ui/form/assets/MarkdownFieldAsset.php b/protected/humhub/modules/ui/form/assets/MarkdownFieldAsset.php deleted file mode 100644 index 3378628f01..0000000000 --- a/protected/humhub/modules/ui/form/assets/MarkdownFieldAsset.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php - - -namespace humhub\modules\ui\form\assets; - - -use humhub\assets\BootstrapMarkdownAsset; -use humhub\components\assets\AssetBundle; - -/** - * Class MarkdownFieldAsset - * @package humhub\modules\ui\form\assets - * @deprecated since 1.5 Use `humhub\modules\content\widgets\richtext\RichTextField` - */ -class MarkdownFieldAsset extends AssetBundle -{ - public $sourcePath = '@ui/form/resources'; - - public $css = [ - 'css/bootstrap-markdown-override.css' - ]; - - public $js = [ - 'js/markdownEditor.js', - 'js/humhub.ui.markdown.js' - ]; - - public $depends = [ - BootstrapMarkdownAsset::class - ]; -} diff --git a/protected/humhub/modules/ui/form/resources/css/bootstrap-markdown-override.css b/protected/humhub/modules/ui/form/resources/css/bootstrap-markdown-override.css deleted file mode 100644 index 8f2842b5b9..0000000000 --- a/protected/humhub/modules/ui/form/resources/css/bootstrap-markdown-override.css +++ /dev/null @@ -1,35 +0,0 @@ -.md-editor { - border-radius: 3px; - border-width: 2px; -} - -.md-editor.active { - outline: 0px none; - box-shadow: none; -} - -.md-editor > textarea { - min-height: 100px; - padding: 10px; - font-family: inherit; - color: #555555; - font-size: 14px; - background: #fff !important; - border-top: none; - border-bottom: none; -} - -.md-editor textarea { - padding:10px; -} - -.md-editor > textarea:focus { - border: 0; - outline: 0; - box-shadow: none; - } - - .md-preview { - padding-right:10px; - padding-left:10px; - } \ No newline at end of file diff --git a/protected/humhub/modules/ui/form/resources/js/humhub.ui.markdown.js b/protected/humhub/modules/ui/form/resources/js/humhub.ui.markdown.js deleted file mode 100644 index 8764c9d8d7..0000000000 --- a/protected/humhub/modules/ui/form/resources/js/humhub.ui.markdown.js +++ /dev/null @@ -1,141 +0,0 @@ -/* - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - * - */ - -/** - * Module for creating an manipulating modal dialoges. - * Normal layout of a dialog: - * - * <div class="modal"> - * <div class="modal-dialog"> - * <div class="modal-content"> - * <div class="modal-header"></div> - * <div class="modal-body"></div> - * <div class="modal-footer"></div> - * </div> - * </div> - * </div> - * - * @param {type} param1 - * @param {type} param2 - */ -humhub.module('ui.markdown', function (module, require, $) { - var util = require('util'); - var object = util.object; - var Widget = require('ui.widget').Widget; - var client = require('client'); - var modal = require('ui.modal'); - - var MarkdownField = function (node, options) { - Widget.call(this, node, options); - }; - - object.inherits(MarkdownField, Widget); - - MarkdownField.prototype.insert = function(e, chunk) { - var selected = e.getSelection(); - var content = e.getContent(); - - e.replaceSelection(chunk); - - var cursor = selected.start; - e.setSelection(cursor, cursor + chunk.length); - }; - - MarkdownField.prototype.getUploadButtonWidget = function() { - var uploadWidget = Widget.instance('#markdown-file-upload'); - uploadWidget.$form = $(this.$.closest('form')); - - if (this.options.filesInputName) { - uploadWidget.options.uploadSubmitName = this.options.filesInputName; - } else { - uploadWidget.options.uploadSubmitName = uploadWidget.data('upload-submit-name'); - } - return uploadWidget; - }; - - MarkdownField.prototype.init = function () { - var that = this; - this.$.markdown({ - iconlibrary: 'fa', - resize: 'vertical', - additionalButtons: [ - [{ - name: "groupCustom", - data: [{ - name: "cmdLinkWiki", - title: "URL/Link", - icon: {glyph: 'glyphicon glyphicon-link', fa: 'fa fa-link', 'fa-3': 'icon-link'}, - callback: function (e) { - var linkModal = modal.get('#markdown-modal-add-link'); - $titleInput = linkModal.$.find('.linkTitle'); - $urlInput = linkModal.$.find('.linkTarget'); - - linkModal.show(); - - $titleInput.val(e.getSelection().text); - if ($titleInput.val() == "") { - $titleInput.focus(); - } else { - $urlInput.focus(); - } - - linkModal.$.find('.addLinkButton').off('click').on('click', function () { - that.insert(e, "[" + $titleInput.val() + "](" + $urlInput.val() + ")"); - linkModal.close(); - }); - - linkModal.$.on('hide.bs.modal', function (e) { - $titleInput.val(""); - $urlInput.val(""); - }) - } - }, - { - name: "cmdImgWiki", - title: "Image/File", - icon: {glyph: 'glyphicon glyphicon-picture', fa: 'fa fa-picture-o', 'fa-3': 'icon-picture'}, - callback: function (e) { - - var fileModal = modal.get('#markdown-modal-file-upload'); - fileModal.show(); - - that.getUploadButtonWidget().off('uploadEnd').on('uploadEnd', function(evt, response) { - fileModal.close(); - $.each(response.result.files, function(i, file) { - var chunk = (file.mimeType.substring(0, 6) == "image/") ? '!' : ''; - chunk += "[" + file.name + "](file-guid-" + file.guid + ")"; - that.insert(e, chunk); - e.setSelection(e.end, 0); - }); - }); - } - }, - ] - }] - ], - reorderButtonGroups: ["groupFont", "groupCustom", "groupMisc", "groupUtil"], - onPreview: function (e) { - var options = { - dataType: 'html', - data : { - markdown: e.getContent() - } - }; - - client.post(that.options.previewUrl, options).then(function(response) { - that.$.siblings('.md-preview').html(response.html); - }); - - return "<div><div class='loader'></div></div>"; - } - }); - }; - - module.export({ - MarkdownField: MarkdownField - }); -}); diff --git a/protected/humhub/modules/ui/form/resources/js/markdownEditor.js b/protected/humhub/modules/ui/form/resources/js/markdownEditor.js deleted file mode 100644 index 906395b5da..0000000000 --- a/protected/humhub/modules/ui/form/resources/js/markdownEditor.js +++ /dev/null @@ -1,167 +0,0 @@ -// Newly uploaded file -var newFile = ""; - -/** - * @deprecated since 1.5 - */ -function initMarkdownEditor(elementId) { - - if (!$('#addFileModal_' + elementId).length) { - $("body").append($("#markdownEditor_dialogs_" + elementId).html()); - } - - $("#" + elementId).markdown({ - iconlibrary: 'fa', - resize: 'vertical', - additionalButtons: [ - [{ - name: "groupCustom", - data: [{ - name: "cmdLinkWiki", - title: "URL/Link", - icon: {glyph: 'glyphicon glyphicon-link', fa: 'fa fa-link', 'fa-3': 'icon-link'}, - callback: function (e) { - addLinkModal = $('#addLinkModal_' + elementId); - linkTitleField = addLinkModal.find('.linkTitle'); - linkTargetField = addLinkModal.find('.linkTarget'); - - - addLinkModal.find(".close").off('click'); - - - addLinkModal.modal('show'); - - linkTitleField.val(e.getSelection().text); - if (linkTitleField.val() == "") { - linkTitleField.focus(); - } else { - linkTargetField.focus(); - } - - addLinkModal.find('.addLinkButton').off('click'); - addLinkModal.find('.addLinkButton').on('click', function () { - chunk = "[" + linkTitleField.val() + "](" + linkTargetField.val() + ")"; - selected = e.getSelection(), content = e.getContent(), - e.replaceSelection(chunk); - cursor = selected.start; - e.setSelection(cursor, cursor + chunk.length); - addLinkModal.modal('hide') - }); - - addLinkModal.on('hide.bs.modal', function (ee) { - linkTitleField.val(""); - linkTargetField.val(""); - }) - } - }, - { - name: "cmdImgWiki", - title: "Image/File", - icon: {glyph: 'glyphicon glyphicon-picture', fa: 'fa fa-picture-o', 'fa-3': 'icon-picture'}, - callback: function (e) { - - addFileModal = $('#addFileModal_' + elementId); - addFileModal.modal('show'); - addFileModal.find(".uploadForm").show(); - addFileModal.find(".uploadProgress").hide(); - - addFileModal.on('hide.bs.modal', function (ee) { - if (newFile != "") { - var chunk; - if (newFile.mimeType.substring(0, 6) == "image/") { - chunk = "![" + newFile.name + "](file-guid-" + newFile.guid + ") "; - } else { - chunk = "[" + newFile.name + "](file-guid-" + newFile.guid + ") "; - } - insertAtCaret(e.$textarea[0], chunk) - newFile = ""; - } - }) - } - }, - ] - }] - ], - reorderButtonGroups: ["groupFont", "groupCustom", "groupMisc", "groupUtil"], - onPreview: function (e) { - $.ajax({ - type: "POST", - url: markdownPreviewUrl, - data: { - markdown: e.getContent(), - } - }).done(function (previewHtml) { - $('#markdownpreview_' + elementId).html(previewHtml); - }); - var previewContent = "<div id='markdownpreview_" + elementId + "'><div class='loader'></div></div>"; - return previewContent; - } - }); - - $('#addFileModal_' + elementId).find(".uploadProgress").hide(); - $('#addFileModal_' + elementId).find('.fileUploadButton').fileupload({ - dataType: 'json', - done: function (e, data) { - $.each(data.result.files, function (index, file) { - addFileModal = $('#addFileModal_' + elementId); - if (!file.error) { - newFile = file; - hiddenValueField = $('#fileUploaderHiddenGuidField_' + elementId); - hiddenValueField.val(hiddenValueField.val() + "," + file.guid); - addFileModal.modal('hide'); - } else { - alert("file upload error"); - } - }); - }, - progressall: function (e, data) { - newFile = ""; - addFileModal = $('#addFileModal_' + elementId); - - var progress = parseInt(data.loaded / data.total * 100, 10); - addFileModal.find(".uploadForm").hide(); - addFileModal.find(".uploadProgress").show(); - if (progress == 100) { - addFileModal.find(".uploadProgress").hide(); - addFileModal.find(".uploadForm").hide(); - } - } - }).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled'); - -} -function insertAtCaret(txtarea, text) { - if (!txtarea) { - return; - } - - var scrollPos = txtarea.scrollTop; - var strPos = 0; - var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ? "ff" : (document.selection ? "ie" : false)); - if (br == "ie") { - txtarea.focus(); - var range = document.selection.createRange(); - range.moveStart('character', -txtarea.value.length); - strPos = range.text.length; - } else if (br == "ff") { - strPos = txtarea.selectionStart; - } - - var front = (txtarea.value).substring(0, strPos); - var back = (txtarea.value).substring(strPos, txtarea.value.length); - txtarea.value = front + text + back; - strPos = strPos + text.length; - if (br == "ie") { - txtarea.focus(); - var ieRange = document.selection.createRange(); - ieRange.moveStart('character', -txtarea.value.length); - ieRange.moveStart('character', strPos); - ieRange.moveEnd('character', 0); - ieRange.select(); - } else if (br == "ff") { - txtarea.selectionStart = strPos; - txtarea.selectionEnd = strPos; - txtarea.focus(); - } - - txtarea.scrollTop = scrollPos; -} diff --git a/protected/humhub/modules/ui/form/widgets/Markdown.php b/protected/humhub/modules/ui/form/widgets/Markdown.php deleted file mode 100644 index 7f7a24c30d..0000000000 --- a/protected/humhub/modules/ui/form/widgets/Markdown.php +++ /dev/null @@ -1,150 +0,0 @@ -<?php -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - * - */ - - -namespace humhub\modules\ui\form\widgets; - -use humhub\libs\Html; -use humhub\modules\file\widgets\UploadButton; -use humhub\modules\ui\form\assets\MarkdownFieldAsset; -use yii\helpers\Url; - -/** - * Simple Markdown Editor form fields. - * - * @deprecated since 1.5 use `humhub\modules\content\widgets\richtext\RichTextField` instead - * @package humhub\widgets - * @since 1.2.2 - */ -class Markdown extends JsInputWidget -{ - /** - * @inheritdoc - */ - public $jsWidget = 'ui.markdown.MarkdownField'; - - /** - * @var int defines the HTML rows attribute of the textarea - */ - public $rows = 3; - - /** - * @var string markdown preview url - */ - public $previewUrl; - - /** - * HMarkdown parser class used for preview - * - * @var string - */ - public $parserClass = "HMarkdown"; - - /** - * @var bool show label - */ - public $label = false; - - /** - * @var string defines the name of the hidden input name for uploaded files if not set the UploadButton default is used - * @see UploadButton - */ - public $filesInputName; - - /** - * Can defined in addition to $fileAttribute to change the form model of the file from $model to $fileModel. - * Note: this is only affects the formName for the file upload. - * @var string - */ - public $fileModel; - - /** - * Can be set if $model is defined, to create a loadable fileInput name which is respected in Model::load() - * Note: this is only affects the formName for the file upload. - * @var string - */ - public $fileAttribute; - - /** - * @var boolean if set to true the markdown field will be disabled - */ - public $disabled = false; - - /** - * @var boolean if set to true the markdown field will set to readonly - */ - public $readonly = false; - - /** - * @var string - */ - public $placeholder; - - /** - * @inheritdoc - */ - public $fadeIn = 'fast'; - - /** - * @inheritdoc - */ - public $init = true; - - public function init() - { - if (empty($this->previewUrl)) { - $this->previewUrl = Url::toRoute(['/markdown/preview', 'parser' => $this->parserClass]); - } - } - - public function run() - { - MarkdownFieldAsset::register($this->view); - - if ($this->placeholder === null && $this->hasModel()) { - $this->placeholder = $this->model->getAttributeLabel($this->attribute); - } - - if ($this->form != null) { - $textArea = $this->form->field($this->model, $this->attribute)->textarea($this->getOptions())->label($this->label); - } elseif ($this->model != null) { - $textArea = Html::activeTextarea($this->model, $this->attribute, $this->getOptions()); - } else { - $textArea = Html::textarea($this->name, $this->value, $this->getOptions()); - } - - return $textArea; - } - - public function getAttributes() - { - return [ - 'rows' => $this->rows, - 'disabled' => $this->disabled, - 'readonly' => $this->readonly, - 'placeholder' => $this->placeholder, - 'class' => 'form-control' - ]; - } - - public function getData() - { - if (empty($this->fileModel)) { - $this->fileModel = $this->model; - } - - if ($this->model && $this->fileAttribute) { - $this->filesInputName = $this->fileModel->formName() . '[' . $this->fileAttribute . '][]'; - } - - return [ - 'preview-url' => $this->previewUrl, - 'files-input-name' => !empty($this->filesInputName) ? $this->filesInputName : null - ]; - } -} diff --git a/protected/humhub/modules/ui/form/widgets/MarkdownModals.php b/protected/humhub/modules/ui/form/widgets/MarkdownModals.php deleted file mode 100644 index b281f91b2a..0000000000 --- a/protected/humhub/modules/ui/form/widgets/MarkdownModals.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - * - */ - -namespace humhub\modules\ui\form\widgets; - -use humhub\components\Widget; -use humhub\widgets\LayoutAddons; - -/** - * Class MarkdownModals provides modals which are added used by the Markdown widget. - * The widget is automatically added to the layout addons. - * - * @see LayoutAddons - * @since 1.3 - */ -class MarkdownModals extends Widget -{ - public function run() - { - return $this->render('markdownModals'); - } - -} diff --git a/protected/humhub/modules/ui/form/widgets/views/markdownModals.php b/protected/humhub/modules/ui/form/widgets/views/markdownModals.php deleted file mode 100644 index 6d7341059c..0000000000 --- a/protected/humhub/modules/ui/form/widgets/views/markdownModals.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -use humhub\modules\file\widgets\UploadButton; -use humhub\widgets\Button; -use humhub\widgets\ModalButton; -use humhub\widgets\ModalDialog; - -?> - -<div class="modal modal-top" id="markdown-modal-file-upload" tabindex="-1" role="dialog" style="z-index:99999" aria-hidden="true"> - <?php ModalDialog::begin(['header' => Yii::t('UiModule.markdownEditor', 'Add image/file')])?> - <div class="modal-body"> - - <div class="uploadForm"> - <?= UploadButton::widget([ - 'id' => 'markdown-file-upload', - 'label' => true, - 'tooltip' => false, - 'progress' => '#markdown-modal-upload-progress', - 'cssButtonClass' => 'btn-default btn-sm', - 'dropZone' => '#markdown-modal-file-upload', - 'hideInStream' => true - ]) ?> - </div> - - <br> - - <div id="markdown-modal-upload-progress" style="display:none"></div> - - </div> - <div class="modal-footer"> - <?= ModalButton::cancel(Yii::t('base', 'Close')) ?> - </div> - <?php ModalDialog::end() ?> -</div> - -<div class="modal modal-top" id="markdown-modal-add-link" tabindex="-1" role="dialog" style="z-index:99999" aria-hidden="true"> - <?php ModalDialog::begin(['header' => Yii::t('UiModule.markdownEditor', 'Add link')])?> - <div class="modal-body"> - <div class="form-group"> - <label for="addLinkTitle"><?= Yii::t('UiModule.markdownEditor', 'Title'); ?></label> - <input type="text" class="form-control linkTitle" - placeholder="<?= Yii::t('UiModule.markdownEditor', 'Title of your link'); ?>"> - </div> - <div class="form-group"> - <label for="addLinkTarget"><?= Yii::t('UiModule.markdownEditor', 'Target'); ?></label> - <input type="text" class="form-control linkTarget" - placeholder="<?= Yii::t('UiModule.markdownEditor', 'Enter a url (e.g. http://example.com)'); ?>"> - </div> - </div> - <div class="modal-footer"> - <?= ModalButton::cancel(Yii::t('base', 'Close')) ?> - <?= Button::primary(Yii::t('UiModule.markdownEditor', 'Add link'))->cssClass('addLinkButton')->loader(false) ?> - </div> - <?php ModalDialog::end() ?> -</div> diff --git a/protected/humhub/modules/ui/messages/am/markdownEditor.php b/protected/humhub/modules/ui/messages/am/markdownEditor.php deleted file mode 100644 index 6145f663cf..0000000000 --- a/protected/humhub/modules/ui/messages/am/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'ምስል/ፋይል ያክሉ', - 'Add link' => 'ማስፈንጠሪያ ያክሉ', - 'Bold' => 'ጉልህ', - 'Close' => 'ዝጋ', - 'Code' => 'ኮድ', - 'Enter a url (e.g. http://example.com)' => 'url ያስገቡ (ምሳሌ፦ http://example.com)', - 'Heading' => 'ራስጌ', - 'Image' => 'ምስል', - 'Image/File' => 'ምስል/ ፎቶ', - 'Insert Hyperlink' => 'ማስተሳሰሪያ ያስገቡ', - 'Insert Image Hyperlink' => 'የምስል ማስተሳሰሪያ ያስገቡ', - 'Italic' => 'አግድሞሽ', - 'List' => 'ዝርዝር', - 'Ordered List' => 'ቅድምተከተላዊ ዝርዝር', - 'Please wait while uploading...' => 'እባክዎን እስኪጭን ድረስ ይጠብቁ', - 'Preview' => 'ቅድመዕይታ', - 'Quote' => 'ጥቅስ/አባባል', - 'Target' => 'ዒላማ', - 'Title' => 'ርዕስ', - 'Title of your link' => 'የማስፈንጠሪያዎ ርዕስ', - 'URL/Link' => 'URL/ማስፈንጠሪያ', - 'Unordered List' => 'ቅደም ተከተል የሌለው ዝርዝር', - 'code text here' => 'ሚስጥራዊ ፅሁፍ እዚህ ያስፍሩ', - 'emphasized text' => 'አፅእኖት የተሰጠው ፅሁፍ', - 'enter image description here' => 'ተደጋጋሚ የምስል ማስፈሪያ እዚህ ያስገቡ', - 'enter image title here' => 'ለምስሉ ርዕስ እዚህ ያስገቡ', - 'enter link description here' => 'ለማስፈንጠሪያው መግለጫ እዚህ ያስገቡ', - 'heading text' => 'የራስጌ ፅሁፍ', - 'list text here' => 'ፅሁፍ በዚህ ይዘርዝሩ', - 'quote here' => 'በዚህ ይጥቀሱ', - 'strong text' => 'ደማቅ ፅሁፍ', -); diff --git a/protected/humhub/modules/ui/messages/an/markdownEditor.php b/protected/humhub/modules/ui/messages/an/markdownEditor.php deleted file mode 100644 index 04fea94c0b..0000000000 --- a/protected/humhub/modules/ui/messages/an/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Adhibir imachen/fichero', - 'Add link' => 'Adhibir vinclo', - 'Bold' => 'Negrita', - 'Close' => 'Zarrar', - 'Code' => 'Codigo', - 'Enter a url (e.g. http://example.com)' => 'Mete una dirección (eix. http://eixemplo.com)', - 'Heading' => 'Cabecera', - 'Image' => 'Imachen', - 'Image/File' => 'Imachen/Fichero', - 'Insert Hyperlink' => 'Insertar hipervinclo', - 'Insert Image Hyperlink' => 'Insertar hipervinclo d\'imachen', - 'Italic' => 'Cursiva', - 'List' => 'Lista', - 'Ordered List' => 'Lista ordenada', - 'Please wait while uploading...' => 'Aguarda mientres se puya...', - 'Preview' => 'Preveyer', - 'Quote' => 'Comillas', - 'Target' => 'Obchectivo', - 'Title' => 'Titulek', - 'Title of your link' => 'Títol d\'o vinclo', - 'URL/Link' => 'URL/Vinclo', - 'Unordered List' => 'Lista desordenada', - 'code text here' => 'texto d\'o codigo aquí', - 'emphasized text' => 'texto destacau', - 'enter image description here' => 'mete a descripción d\'a imachen aquí', - 'enter image title here' => 'mete o títol d\'a imachen aquí', - 'enter link description here' => 'mete a descripción d\'o vinclo aquí', - 'heading text' => 'texto de cabecera', - 'list text here' => 'texto d\'a lista aquí', - 'quote here' => 'cita aquí', - 'strong text' => 'texto en negrita', -); diff --git a/protected/humhub/modules/ui/messages/ar/markdownEditor.php b/protected/humhub/modules/ui/messages/ar/markdownEditor.php deleted file mode 100644 index f9e4dc56ed..0000000000 --- a/protected/humhub/modules/ui/messages/ar/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'إضافة صورة أو ملف', - 'Add link' => 'إضافة وصلة', - 'Bold' => 'عريض', - 'Close' => 'اغلاق', - 'Code' => 'كود', - 'Enter a url (e.g. http://example.com)' => 'اكتب وصلة مثلاُ http://example.com', - 'Heading' => 'عنوان قائمة', - 'Image' => 'صورة', - 'Image/File' => 'صورة/ملف', - 'Insert Hyperlink' => 'أدخل وصلة', - 'Insert Image Hyperlink' => 'أدخل وصلة صورة', - 'Italic' => 'مائل', - 'List' => 'قائمة', - 'Ordered List' => '', - 'Please wait while uploading...' => 'الرجاء الإنتظار ريثما يتم الرفع', - 'Preview' => 'استعراض', - 'Quote' => 'اقتباس', - 'Target' => 'الهدف', - 'Title' => 'العنوان', - 'Title of your link' => 'عنوان الوصلة', - 'URL/Link' => 'الوصلة', - 'Unordered List' => '', - 'code text here' => 'ضع كود هنا', - 'emphasized text' => 'النص المبالغ فيه', - 'enter image description here' => 'الرجاء إدخال وصف الصورة هنا', - 'enter image title here' => 'ضع عنوان الصورة هنا', - 'enter link description here' => 'ضع وصف الوصلة هنا', - 'heading text' => 'نص العنوان', - 'list text here' => 'نص القائمة هنا', - 'quote here' => 'نص الاقتباس هنا', - 'strong text' => 'خط عريض', -); diff --git a/protected/humhub/modules/ui/messages/bg/markdownEditor.php b/protected/humhub/modules/ui/messages/bg/markdownEditor.php deleted file mode 100644 index 25773c6708..0000000000 --- a/protected/humhub/modules/ui/messages/bg/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Добавете изображение/файл', - 'Add link' => 'Добавяне на връзка', - 'Bold' => 'Удебелен', - 'Close' => 'Затвори', - 'Code' => 'Код', - 'Enter a url (e.g. http://example.com)' => 'Въведете URL (напр. http://example.com)', - 'Heading' => 'Заглавие', - 'Image' => 'Изображение', - 'Image/File' => 'Изображение /файл', - 'Insert Hyperlink' => 'Вмъкни хиперлинк', - 'Insert Image Hyperlink' => 'Вкъкни изображение на хиперлинк', - 'Italic' => 'Курсив', - 'List' => 'Списък', - 'Ordered List' => 'Подреден списък', - 'Please wait while uploading...' => 'Моля, изчакайте, докато качвате...', - 'Preview' => 'Визуализация', - 'Quote' => 'Цитирай', - 'Target' => 'Цел', - 'Title' => 'Звание', - 'Title of your link' => 'Име на твоята връзка', - 'URL/Link' => 'URL/Връзка', - 'Unordered List' => 'Неподреден списък', - 'code text here' => 'код на текста тук', - 'emphasized text' => 'подчертан текст', - 'enter image description here' => 'въведете тук описание на изображението', - 'enter image title here' => 'въведете заглавието на изображението тук', - 'enter link description here' => 'въведете тук описание на връзката', - 'heading text' => 'заглавен текст', - 'list text here' => 'списъчен текст тук', - 'quote here' => 'цитирай тук', - 'strong text' => 'удебелен текст', -); diff --git a/protected/humhub/modules/ui/messages/br/markdownEditor.php b/protected/humhub/modules/ui/messages/br/markdownEditor.php deleted file mode 100644 index f170788870..0000000000 --- a/protected/humhub/modules/ui/messages/br/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => 'Serriñ', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => '', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/ca/markdownEditor.php b/protected/humhub/modules/ui/messages/ca/markdownEditor.php deleted file mode 100644 index abcda4901e..0000000000 --- a/protected/humhub/modules/ui/messages/ca/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Afegeix una imatge/arxiu', - 'Add link' => 'Afegeix un enllaç', - 'Bold' => 'Negreta', - 'Close' => 'Tanca', - 'Code' => 'Codi', - 'Enter a url (e.g. http://example.com)' => 'Entra un enllaç (ex, http://exemple.cat)', - 'Heading' => 'Títol', - 'Image' => 'Imatge', - 'Image/File' => 'Imatge/Arxiu', - 'Insert Hyperlink' => 'Inserir Hipervincle', - 'Insert Image Hyperlink' => 'Inserir Hipervincle d\'imatge', - 'Italic' => 'Cursiva', - 'List' => 'Llista', - 'Ordered List' => 'Llista ordenada', - 'Please wait while uploading...' => 'Si us plau, espereu mentre es carreguen les dades ...', - 'Preview' => 'Previsualització', - 'Quote' => 'Cita', - 'Target' => 'Objectiu', - 'Title' => 'Títol', - 'Title of your link' => 'Títol del teu enllaç', - 'URL/Link' => 'URL/Enllaç', - 'Unordered List' => 'Llista desordenada', - 'code text here' => 'text del codi aquí', - 'emphasized text' => 'text subratllat', - 'enter image description here' => 'entra la descripció de l\'imatge aquí', - 'enter image title here' => 'entra el títol de l\'imatge aquí', - 'enter link description here' => 'entra la descripció de l\'enllaç aquí', - 'heading text' => 'text del títol', - 'list text here' => 'text de la llista aquí', - 'quote here' => 'escriu la cita aquí', - 'strong text' => 'text en negreta', -); diff --git a/protected/humhub/modules/ui/messages/cs/markdownEditor.php b/protected/humhub/modules/ui/messages/cs/markdownEditor.php deleted file mode 100644 index 09e5b2c7b5..0000000000 --- a/protected/humhub/modules/ui/messages/cs/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Přidat obrázek/soubor', - 'Add link' => 'Přidat odkaz', - 'Bold' => 'Tučně', - 'Close' => 'Zavřít', - 'Code' => 'Kód', - 'Enter a url (e.g. http://example.com)' => 'Zadejte URL adresu (např. http://example.com)', - 'Heading' => 'Nadpis', - 'Image' => 'Obrázek', - 'Image/File' => 'Obrázek/soubor', - 'Insert Hyperlink' => 'Vložit odkaz', - 'Insert Image Hyperlink' => 'Vložit odkaz na obrázek', - 'Italic' => 'Kurzíva', - 'List' => 'Seznam', - 'Ordered List' => 'Objednaný seznam', - 'Please wait while uploading...' => 'Prosím počkejte chvíli, nahrávám soubor...', - 'Preview' => 'Náhled', - 'Quote' => 'Citace', - 'Target' => 'Cíl odkazu', - 'Title' => 'Název', - 'Title of your link' => 'Napište titulek odkazu', - 'URL/Link' => 'URL adresa/odkaz', - 'Unordered List' => 'Neřízený seznam', - 'code text here' => 'zde je místo pro kód', - 'emphasized text' => 'text kurzívou', - 'enter image description here' => 'zde je místo pro popis obrázku', - 'enter image title here' => 'zde je místo pro titulek obrázku', - 'enter link description here' => 'zde je místo pro popis odkazu', - 'heading text' => 'text nadpisu', - 'list text here' => 'odrážka', - 'quote here' => 'zde je místo pro citaci', - 'strong text' => 'tučný text', -); diff --git a/protected/humhub/modules/ui/messages/cy/markdownEditor.php b/protected/humhub/modules/ui/messages/cy/markdownEditor.php deleted file mode 100644 index 902d07a9b1..0000000000 --- a/protected/humhub/modules/ui/messages/cy/markdownEditor.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -return [ - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => '', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -]; diff --git a/protected/humhub/modules/ui/messages/da/markdownEditor.php b/protected/humhub/modules/ui/messages/da/markdownEditor.php deleted file mode 100644 index c4e8c1c0dd..0000000000 --- a/protected/humhub/modules/ui/messages/da/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Tilføj billede/fil', - 'Add link' => 'Tilføj link', - 'Bold' => 'Fed', - 'Close' => 'Luk', - 'Code' => 'Kode', - 'Enter a url (e.g. http://example.com)' => 'Indtast en url (e.g. http://example.com)', - 'Heading' => 'Overskrift', - 'Image' => 'Billede', - 'Image/File' => 'Billede/fil', - 'Insert Hyperlink' => 'Indsæt Hyperlink', - 'Insert Image Hyperlink' => 'Indsæt Billede Hyperlink', - 'Italic' => 'Kursiv', - 'List' => 'Liste', - 'Ordered List' => '', - 'Please wait while uploading...' => 'Vent venligst, uploader...', - 'Preview' => 'Forhåndsvisning', - 'Quote' => 'Citat', - 'Target' => 'Mål', - 'Title' => 'Titel', - 'Title of your link' => 'Titel til dit link', - 'URL/Link' => 'URL/Link', - 'Unordered List' => '', - 'code text here' => 'kode tekst her', - 'emphasized text' => 'understreget tekst', - 'enter image description here' => 'skriv billede beskrivelse her', - 'enter image title here' => 'skriv billede titel her', - 'enter link description here' => 'skriv link beskrivelse her', - 'heading text' => 'overskrifts tekst', - 'list text here' => 'liste tekst her', - 'quote here' => 'citat her', - 'strong text' => 'fed tekst', -); diff --git a/protected/humhub/modules/ui/messages/de/markdownEditor.php b/protected/humhub/modules/ui/messages/de/markdownEditor.php deleted file mode 100644 index 1d3faa14a9..0000000000 --- a/protected/humhub/modules/ui/messages/de/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Bild/Datei einfügen', - 'Add link' => 'Link einfügen', - 'Bold' => 'Fett', - 'Close' => 'Schließen', - 'Code' => 'Code', - 'Enter a url (e.g. http://example.com)' => 'Trage eine URL ein (z. B. http://example.com)', - 'Heading' => 'Überschrift', - 'Image' => 'Bild', - 'Image/File' => 'Bild/Datei', - 'Insert Hyperlink' => 'Hyperlink einfügen', - 'Insert Image Hyperlink' => 'Bild-Hyperlink einfügen', - 'Italic' => 'Kursiv', - 'List' => 'Liste', - 'Ordered List' => 'Geordnete Liste', - 'Please wait while uploading...' => 'Wird hochgeladen ...', - 'Preview' => 'Vorschau', - 'Quote' => 'Zitat', - 'Target' => 'Ziel', - 'Title' => 'Titel', - 'Title of your link' => 'Titel des Links', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Ungeordnete Liste', - 'code text here' => 'Code hier einfügen', - 'emphasized text' => 'unterstrichener Text', - 'enter image description here' => 'Gib hier eine Bildbeschreibung ein', - 'enter image title here' => 'Gib hier einen Bildtitel ein', - 'enter link description here' => 'Gib hier eine Linkbeschreibung ein', - 'heading text' => 'Überschrift', - 'list text here' => 'Text hier einfügen', - 'quote here' => 'hier zitieren', - 'strong text' => 'Fetter Text', -); diff --git a/protected/humhub/modules/ui/messages/el/markdownEditor.php b/protected/humhub/modules/ui/messages/el/markdownEditor.php deleted file mode 100644 index f66116d108..0000000000 --- a/protected/humhub/modules/ui/messages/el/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => 'Κλείσιμο', - 'Code' => 'Κώδικας', - 'Enter a url (e.g. http://example.com)' => 'Προσθήκη url (π.χ. https://example.com)', - 'Heading' => 'Επικεφαλίδα', - 'Image' => 'Εικόνα', - 'Image/File' => 'Εικόνα/Αρχείο', - 'Insert Hyperlink' => 'Εισαγωγή Συνδέσμου', - 'Insert Image Hyperlink' => 'Εισαγωγή Συνδέσμου Εικόνας', - 'Italic' => '', - 'List' => '', - 'Ordered List' => 'Ταξινομημένη Λίστα', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => 'Στόχος', - 'Title' => 'Τίτλος', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/es/markdownEditor.php b/protected/humhub/modules/ui/messages/es/markdownEditor.php deleted file mode 100644 index e23368b908..0000000000 --- a/protected/humhub/modules/ui/messages/es/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Añadir imagen/archivo', - 'Add link' => 'Añadir enlace', - 'Bold' => 'Negrita', - 'Close' => 'Cerrar', - 'Code' => 'Código', - 'Enter a url (e.g. http://example.com)' => 'Añade un enlace (ej. http://ejemplo.com)', - 'Heading' => 'Título', - 'Image' => 'Imagen', - 'Image/File' => 'Imagen/Archivo', - 'Insert Hyperlink' => 'Insertar hipervínculo', - 'Insert Image Hyperlink' => 'Insertar imagen', - 'Italic' => 'Cursiva', - 'List' => 'Lista', - 'Ordered List' => 'Lista ordenada', - 'Please wait while uploading...' => 'Por favor, espera mientras se sube...', - 'Preview' => 'Previsualizar', - 'Quote' => 'Cita', - 'Target' => 'Target', - 'Title' => 'Título', - 'Title of your link' => 'Título de tu enlace', - 'URL/Link' => 'URL/Enlace', - 'Unordered List' => 'Lista sin orden', - 'code text here' => 'Texto con código aquí', - 'emphasized text' => 'Texto subrayado', - 'enter image description here' => 'Escribe la descripción de la imagen aquí', - 'enter image title here' => 'Escribe el título de la imagen aquí', - 'enter link description here' => 'Escribe la descripción del enlace aquí', - 'heading text' => 'Texto de cabecera', - 'list text here' => 'Lista de texto aquí', - 'quote here' => 'Citar aquí', - 'strong text' => 'Texto en negrita', -); diff --git a/protected/humhub/modules/ui/messages/et/markdownEditor.php b/protected/humhub/modules/ui/messages/et/markdownEditor.php deleted file mode 100644 index 902d07a9b1..0000000000 --- a/protected/humhub/modules/ui/messages/et/markdownEditor.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -return [ - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => '', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -]; diff --git a/protected/humhub/modules/ui/messages/fa-IR/markdownEditor.php b/protected/humhub/modules/ui/messages/fa-IR/markdownEditor.php deleted file mode 100644 index 8adbceb1ca..0000000000 --- a/protected/humhub/modules/ui/messages/fa-IR/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'افزودن عکس/پرونده', - 'Add link' => 'افزودن پیوند', - 'Bold' => 'پررنگ', - 'Close' => 'بستن', - 'Code' => 'کد', - 'Enter a url (e.g. http://example.com)' => 'یک آدرس وارد کنید (مثلا: http://example.com)', - 'Heading' => 'سرنویس', - 'Image' => 'عکس', - 'Image/File' => 'عکس/افزودن', - 'Insert Hyperlink' => 'درج ابرپیوند', - 'Insert Image Hyperlink' => 'درج ابرپیوند عکس', - 'Italic' => 'ایتالیک', - 'List' => 'فهرست', - 'Ordered List' => 'فهرست شماره‌ای', - 'Please wait while uploading...' => 'لطفا تا بارگذاری صبر کنید...', - 'Preview' => 'پیش‌نمایش', - 'Quote' => 'نقل‌قول', - 'Target' => 'مقصد', - 'Title' => 'عنوان', - 'Title of your link' => 'عنوان پیوند شما', - 'URL/Link' => 'URL/پیوند', - 'Unordered List' => 'فهرست بدون شماره', - 'code text here' => 'متن را اینجا کد کنید', - 'emphasized text' => 'متن تاکیدشده', - 'enter image description here' => 'توضیحات عکس را اینجا وارد کنید', - 'enter image title here' => 'عنوان عکس را اینجا وارد کنید', - 'enter link description here' => 'توضیحات پیوند را اینجا وارد کنید', - 'heading text' => 'متن سرنویس', - 'list text here' => 'متن را اینجا لیست کنید', - 'quote here' => 'اینجا بیان کنید', - 'strong text' => 'متن پررنگ', -); diff --git a/protected/humhub/modules/ui/messages/fi/markdownEditor.php b/protected/humhub/modules/ui/messages/fi/markdownEditor.php deleted file mode 100644 index c504f39f08..0000000000 --- a/protected/humhub/modules/ui/messages/fi/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Lisää kuva/tiedosto', - 'Add link' => 'Lisää linkki', - 'Bold' => 'Lihavointi', - 'Close' => 'Sulje', - 'Code' => 'Koodi', - 'Enter a url (e.g. http://example.com)' => 'Syötä url (esim. https://esimerkki.fi)', - 'Heading' => 'Otsikko', - 'Image' => 'Kuva', - 'Image/File' => 'Kuva/Tiedosto', - 'Insert Hyperlink' => 'Syötä Hyperlinkki', - 'Insert Image Hyperlink' => 'Syötä Kuvan Hyperlinkki', - 'Italic' => 'Kursivointi', - 'List' => 'Lista', - 'Ordered List' => 'Tehtävä Lista', - 'Please wait while uploading...' => 'Odata päivitetään', - 'Preview' => 'Esikatselu', - 'Quote' => 'Lainaa', - 'Target' => 'Kohde', - 'Title' => 'Otsikko', - 'Title of your link' => 'Kirjoita linkkisi', - 'URL/Link' => 'URL/Linkki', - 'Unordered List' => 'Järjestämätön lista', - 'code text here' => 'koodi tähän', - 'emphasized text' => 'Korostettu teksti', - 'enter image description here' => 'kirjoita kuvaus täähän', - 'enter image title here' => 'kirjoita otsikko tähän', - 'enter link description here' => 'syötä linkki tähän', - 'heading text' => 'otsikon teksti', - 'list text here' => 'syötä listan teksti tähän', - 'quote here' => 'syötä korostettu teksti tähän', - 'strong text' => 'vahva teksti', -); diff --git a/protected/humhub/modules/ui/messages/fr/markdownEditor.php b/protected/humhub/modules/ui/messages/fr/markdownEditor.php deleted file mode 100644 index c6acc92902..0000000000 --- a/protected/humhub/modules/ui/messages/fr/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Ajouter une image/fichier', - 'Add link' => 'Ajouter un lien', - 'Bold' => 'Gras', - 'Close' => 'Fermer', - 'Code' => 'Code', - 'Enter a url (e.g. http://example.com)' => 'Entrer une URL (p.ex. http://exemple.com)', - 'Heading' => 'Entête', - 'Image' => 'Image', - 'Image/File' => 'Image/fichier', - 'Insert Hyperlink' => 'Insérer un lien', - 'Insert Image Hyperlink' => 'Insérer un lien vers une image', - 'Italic' => 'Italique', - 'List' => 'Liste', - 'Ordered List' => 'Liste ordonnée', - 'Please wait while uploading...' => 'Veuillez patienter pendant le transfert...', - 'Preview' => 'Prévisualiser', - 'Quote' => 'Citation', - 'Target' => 'Destination', - 'Title' => 'Titre', - 'Title of your link' => 'Titre de votre lien', - 'URL/Link' => 'URL/Lien', - 'Unordered List' => 'Liste désordonnée', - 'code text here' => 'texte code ici', - 'emphasized text' => 'texte mis en valeur', - 'enter image description here' => 'saisir la description de l\'image ici', - 'enter image title here' => 'saisir le titre de l\'image ici', - 'enter link description here' => 'saisir la description du lien ici', - 'heading text' => 'texte d\'entête', - 'list text here' => 'texte de la liste ici', - 'quote here' => 'citation ici', - 'strong text' => 'texte gras', -); diff --git a/protected/humhub/modules/ui/messages/he/markdownEditor.php b/protected/humhub/modules/ui/messages/he/markdownEditor.php deleted file mode 100644 index 5756a13f33..0000000000 --- a/protected/humhub/modules/ui/messages/he/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => 'סגור', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'כותרת', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/hr/markdownEditor.php b/protected/humhub/modules/ui/messages/hr/markdownEditor.php deleted file mode 100644 index ddfdc16177..0000000000 --- a/protected/humhub/modules/ui/messages/hr/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Dodaj sliku / datoteku', - 'Add link' => 'Dodaj poveznicu', - 'Bold' => 'Bold', - 'Close' => 'Zatvori', - 'Code' => 'Kod', - 'Enter a url (e.g. http://example.com)' => 'Unesite URL (e.g. http://example.com)', - 'Heading' => 'Naslov', - 'Image' => 'Slika', - 'Image/File' => 'Slika/Datoteka', - 'Insert Hyperlink' => 'Unesi hyperlink', - 'Insert Image Hyperlink' => 'Unesi hyperlink slike', - 'Italic' => 'Italic', - 'List' => 'Lista', - 'Ordered List' => 'Uređeni popis', - 'Please wait while uploading...' => 'Pričekajte dok se učitava...', - 'Preview' => 'Pregled', - 'Quote' => 'Citat', - 'Target' => 'Cilj', - 'Title' => 'Naziv', - 'Title of your link' => 'Naziv vašeg linka', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Neuređeni popis', - 'code text here' => 'tekst koda ovdje', - 'emphasized text' => 'naglasi tekst', - 'enter image description here' => 'unesite opis slike', - 'enter image title here' => 'unesite naziv slike', - 'enter link description here' => 'unesite opis linka', - 'heading text' => 'tekst naslova', - 'list text here' => 'unesite popis tekstova', - 'quote here' => 'ovdje navedite', - 'strong text' => 'jaki tekst', -); diff --git a/protected/humhub/modules/ui/messages/ht/markdownEditor.php b/protected/humhub/modules/ui/messages/ht/markdownEditor.php deleted file mode 100644 index 3aa17f2410..0000000000 --- a/protected/humhub/modules/ui/messages/ht/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Ajoute foto/dosye', - 'Add link' => 'Ajoute lyen', - 'Bold' => 'Fonse', - 'Close' => 'Fèmen', - 'Code' => 'Kòd', - 'Enter a url (e.g. http://example.com)' => 'Mete yon url (e.g http://example.com)', - 'Heading' => 'Tit', - 'Image' => 'Imaj', - 'Image/File' => 'Imaj/Dosye', - 'Insert Hyperlink' => 'Antre Ipèrtèkst la', - 'Insert Image Hyperlink' => 'Antre Imaj Ipèrtèkst la', - 'Italic' => 'Italik', - 'List' => 'Lis', - 'Ordered List' => '', - 'Please wait while uploading...' => 'Tanpri, ret tan\'n pandan l\'ap upload...', - 'Preview' => 'Apèsi', - 'Quote' => 'Sitasyon', - 'Target' => 'Tagèt', - 'Title' => 'Tit', - 'Title of your link' => 'Tit Link ou a', - 'URL/Link' => 'URL/Link', - 'Unordered List' => '', - 'code text here' => 'Mete Kòd tèks ou a la', - 'emphasized text' => 'Mete aksan sou tèks la', - 'enter image description here' => 'Mete deskripsyon imaj la', - 'enter image title here' => 'Mete tit imaj la', - 'enter link description here' => 'Mete deskripsyon link lan la', - 'heading text' => 'Tit tèks la', - 'list text here' => 'Mete lis tèks ou a la', - 'quote here' => 'Mete sitasyon an la', - 'strong text' => 'Tèks gra', -); diff --git a/protected/humhub/modules/ui/messages/hu/markdownEditor.php b/protected/humhub/modules/ui/messages/hu/markdownEditor.php deleted file mode 100644 index 8629a80bec..0000000000 --- a/protected/humhub/modules/ui/messages/hu/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Kép/fájl hozzáadása', - 'Add link' => 'Link hozzáadása', - 'Bold' => 'Félkövér', - 'Close' => 'Bezár', - 'Code' => 'Kód', - 'Enter a url (e.g. http://example.com)' => 'Írj be egy URL-címet (pl.: http://pelda.hu)', - 'Heading' => 'Fejléc', - 'Image' => 'Kép', - 'Image/File' => 'Kép/fájl', - 'Insert Hyperlink' => 'Link beszúrása', - 'Insert Image Hyperlink' => 'Kép link beszúrása', - 'Italic' => 'Dőlt', - 'List' => 'Lista', - 'Ordered List' => 'Számozott lista', - 'Please wait while uploading...' => 'Várj, feltöltés folyamatban...', - 'Preview' => 'Előnézet', - 'Quote' => 'Idézet', - 'Target' => 'Cél', - 'Title' => 'Cím', - 'Title of your link' => 'Link leírása', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Felsorolás', - 'code text here' => 'kód szövege', - 'emphasized text' => 'dőlt betűs szöveg', - 'enter image description here' => 'kép leírásának megadása', - 'enter image title here' => 'kép címének megadása', - 'enter link description here' => 'link leírásának megadása', - 'heading text' => 'címsor', - 'list text here' => 'felsorolás szövege', - 'quote here' => 'idézet szövege', - 'strong text' => 'félkövér szöveg', -); diff --git a/protected/humhub/modules/ui/messages/id/markdownEditor.php b/protected/humhub/modules/ui/messages/id/markdownEditor.php deleted file mode 100644 index 6916d7e3ea..0000000000 --- a/protected/humhub/modules/ui/messages/id/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Tambah image/file', - 'Add link' => 'Tambah link', - 'Bold' => '', - 'Close' => 'Tutup', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => 'Masukkan sebuah url (misalnya http://contoh.com)', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => 'Masukkan Hyperlink', - 'Insert Image Hyperlink' => 'Masukkan Image Hyperlink', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => 'Harap tunggu selama proses upload...', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'Judul', - 'Title of your link' => 'Judul link anda', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => 'code text disini', - 'emphasized text' => '', - 'enter image description here' => 'masukkan deskripsi image disini', - 'enter image title here' => 'masukkan judul image disini', - 'enter link description here' => 'masukkan deskripsi link disini', - 'heading text' => '', - 'list text here' => 'text list disini', - 'quote here' => 'quote disini', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/it/markdownEditor.php b/protected/humhub/modules/ui/messages/it/markdownEditor.php deleted file mode 100644 index 2d1701dee0..0000000000 --- a/protected/humhub/modules/ui/messages/it/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Aggiungi immagine/file', - 'Add link' => 'Aggiungi link', - 'Bold' => 'Grassetto', - 'Close' => 'Chiudi', - 'Code' => 'Codice', - 'Enter a url (e.g. http://example.com)' => 'Inserisci un URL (es. http://example.com)', - 'Heading' => 'Intestazione', - 'Image' => 'Immagine', - 'Image/File' => 'Immagine/File', - 'Insert Hyperlink' => 'Inserisci un link', - 'Insert Image Hyperlink' => 'Inserisci un link di un\'immagie', - 'Italic' => 'Corsivo', - 'List' => 'Lista', - 'Ordered List' => 'Lista ordinata', - 'Please wait while uploading...' => 'Attendi il caricamento...', - 'Preview' => 'Anteprima', - 'Quote' => 'Quota', - 'Target' => 'Destinazione', - 'Title' => 'Titolo', - 'Title of your link' => 'Titolo del tuo link', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Lista non ordinata', - 'code text here' => 'testo di codice', - 'emphasized text' => 'testo corsivo', - 'enter image description here' => 'scrivi la descrizione dell\'immagine qui', - 'enter image title here' => 'scrivi il titolo dell\'immagine qui', - 'enter link description here' => 'scrivi la descrizione del link qui', - 'heading text' => 'testo d\'intestazione', - 'list text here' => 'elemento lista', - 'quote here' => 'testo quotato', - 'strong text' => 'testo in grassetto', -); diff --git a/protected/humhub/modules/ui/messages/ja/markdownEditor.php b/protected/humhub/modules/ui/messages/ja/markdownEditor.php deleted file mode 100644 index 1c700777b9..0000000000 --- a/protected/humhub/modules/ui/messages/ja/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '画像/ファイルを追加する', - 'Add link' => 'リンクを追加', - 'Bold' => '太字', - 'Close' => '閉じる', - 'Code' => 'コード', - 'Enter a url (e.g. http://example.com)' => 'URLを入力してください(例:http://example.com)', - 'Heading' => '見出し', - 'Image' => '画像', - 'Image/File' => '画像/ファイル', - 'Insert Hyperlink' => 'ハイパーリンクの挿入', - 'Insert Image Hyperlink' => 'イメージハイパーリンクを挿入', - 'Italic' => 'イタリック', - 'List' => 'リスト', - 'Ordered List' => '番号付きリスト', - 'Please wait while uploading...' => 'アップロード中はお待ちください...', - 'Preview' => 'プレビュー', - 'Quote' => '引用', - 'Target' => 'ターゲット', - 'Title' => 'タイトル', - 'Title of your link' => 'リンクのタイトル', - 'URL/Link' => 'URL /リンク', - 'Unordered List' => '順不同リスト', - 'code text here' => 'コードテキストはこちら', - 'emphasized text' => '強調されたテキスト', - 'enter image description here' => 'ここに画像の説明を入力', - 'enter image title here' => 'ここに画像タイトルを入力してください', - 'enter link description here' => 'ここにリンクの説明を入力してください', - 'heading text' => '見出しテキスト', - 'list text here' => 'ここにテキストをリストする', - 'quote here' => 'ここに引用', - 'strong text' => '強いテキスト', -); diff --git a/protected/humhub/modules/ui/messages/ko/markdownEditor.php b/protected/humhub/modules/ui/messages/ko/markdownEditor.php deleted file mode 100644 index 3e7c0b7619..0000000000 --- a/protected/humhub/modules/ui/messages/ko/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '이미지/파일 추가', - 'Add link' => '링크 추가', - 'Bold' => '굵게', - 'Close' => '닫기', - 'Code' => '코드', - 'Enter a url (e.g. http://example.com)' => '링크를 입력해주세요 (예시: http://example.com/)', - 'Heading' => '제목', - 'Image' => '이미지', - 'Image/File' => '이미지/파일', - 'Insert Hyperlink' => '하이퍼링크 삽입', - 'Insert Image Hyperlink' => '이미지 링크 삽입', - 'Italic' => '이탤릭', - 'List' => '리스트', - 'Ordered List' => '', - 'Please wait while uploading...' => '업로드 중입니다. 기다려주세요...', - 'Preview' => '미리보기', - 'Quote' => '인용하기', - 'Target' => '타겟', - 'Title' => '제목', - 'Title of your link' => '링크 제목', - 'URL/Link' => '링크', - 'Unordered List' => '', - 'code text here' => '코드를 적어주세요', - 'emphasized text' => '강조 텍스트', - 'enter image description here' => '이미지 설명을 여기에 적어주세요', - 'enter image title here' => '이미지 제목을 여기에 적어주세요', - 'enter link description here' => '링크 설명을 여기에 적어주세요', - 'heading text' => '제목 텍스트', - 'list text here' => '리스트 내용을 여기에 적어주세요', - 'quote here' => '인용 내용을 여기에 적어주세요', - 'strong text' => '볼드 문자', -); diff --git a/protected/humhub/modules/ui/messages/lt/markdownEditor.php b/protected/humhub/modules/ui/messages/lt/markdownEditor.php deleted file mode 100644 index 9a1ef8add0..0000000000 --- a/protected/humhub/modules/ui/messages/lt/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Pridėti nuotrauką / failą', - 'Add link' => 'įterpti nuorodą', - 'Bold' => 'Paryškinti', - 'Close' => 'Uždaryti', - 'Code' => 'Kodas', - 'Enter a url (e.g. http://example.com)' => 'Pridėti internetinio puslapio adresą (pvz., http://example.com)', - 'Heading' => 'Pavadinimas', - 'Image' => 'Nuotrauka', - 'Image/File' => 'Nuotrauka / failas', - 'Insert Hyperlink' => 'Pridėti nuorodą', - 'Insert Image Hyperlink' => 'Pridėkite nuorodos nuotrauką', - 'Italic' => 'Kursyvas', - 'List' => 'Sarašas', - 'Ordered List' => '', - 'Please wait while uploading...' => 'Palaukite, kol keliami duomenys', - 'Preview' => 'Peržiūra', - 'Quote' => 'Citata', - 'Target' => 'Objektas', - 'Title' => 'Pavadinimas', - 'Title of your link' => 'Jūsu nuorodos pavadinimas', - 'URL/Link' => 'URL/nuoroda', - 'Unordered List' => '', - 'code text here' => 'Koduoti teksta', - 'emphasized text' => 'Pabrėžti tekstą', - 'enter image description here' => 'Įrašyti paveikslėlio apibūdinimą čia', - 'enter image title here' => 'Įrašyti paveikslėlio pavadinimą čia', - 'enter link description here' => 'Įrašyti nuorodos apibūdinimą čia', - 'heading text' => 'Pavadinimas', - 'list text here' => 'Įrašyti tekstą čia', - 'quote here' => 'Cituoti čia', - 'strong text' => 'Paryškintas tekstas', -); diff --git a/protected/humhub/modules/ui/messages/lv/markdownEditor.php b/protected/humhub/modules/ui/messages/lv/markdownEditor.php deleted file mode 100644 index d9b53b2301..0000000000 --- a/protected/humhub/modules/ui/messages/lv/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Pievienot attēlu/failu', - 'Add link' => 'Pievienot saiti', - 'Bold' => 'Treknraksts', - 'Close' => 'Aizvērt', - 'Code' => 'Kods', - 'Enter a url (e.g. http://example.com)' => 'Ievadi url (piem. http://example.com)', - 'Heading' => 'Virsraksts', - 'Image' => 'Attēls', - 'Image/File' => 'Attēls/Fails', - 'Insert Hyperlink' => 'Ievietot hipersaiti', - 'Insert Image Hyperlink' => 'Ievietot attēla hipersaiti', - 'Italic' => 'Slīpraksts', - 'List' => 'Saraksts', - 'Ordered List' => 'Kārtots saraksts', - 'Please wait while uploading...' => 'Uzgaidi, kamēr augšupielādējas...', - 'Preview' => 'Priekšskatīt', - 'Quote' => 'Citāts', - 'Target' => 'Mērķis', - 'Title' => 'Nosaukums', - 'Title of your link' => 'Saites nosaukums', - 'URL/Link' => 'URL/Saite', - 'Unordered List' => 'Nekārtots saraksts', - 'code text here' => 'koda teksts šeit', - 'emphasized text' => 'uzsvērts teksts', - 'enter image description here' => 'ievadi šeit attēla aprakstu', - 'enter image title here' => 'ievadi šeit attēla nosaukumu', - 'enter link description here' => 'ievadi šeit saites aprakstu', - 'heading text' => 'virsraksta teksts', - 'list text here' => 'saraksta teksts šeit', - 'quote here' => 'citāts šeit', - 'strong text' => 'trenknraksta teksts šeit', -); diff --git a/protected/humhub/modules/ui/messages/nb-NO/markdownEditor.php b/protected/humhub/modules/ui/messages/nb-NO/markdownEditor.php deleted file mode 100644 index 7584ab1ddc..0000000000 --- a/protected/humhub/modules/ui/messages/nb-NO/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Legg til bilde/fil', - 'Add link' => 'Legg til link', - 'Bold' => 'Fet', - 'Close' => 'Lukk', - 'Code' => 'Kode', - 'Enter a url (e.g. http://example.com)' => 'Legg til en link (f.eks. http://example.com)', - 'Heading' => 'Tittel', - 'Image' => 'Bilde', - 'Image/File' => 'Bilde/Fil', - 'Insert Hyperlink' => 'Sett inn link', - 'Insert Image Hyperlink' => 'Sett inn bildelink', - 'Italic' => 'Kursiv', - 'List' => 'Liste', - 'Ordered List' => 'nummerert liste', - 'Please wait while uploading...' => 'Vennligst vent mens filen lastes opp...', - 'Preview' => 'Forhåndsvisning', - 'Quote' => 'Sitat', - 'Target' => 'Mål', - 'Title' => 'Tittel', - 'Title of your link' => 'Link tittel', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'punktliste', - 'code text here' => 'Kodetekst her', - 'emphasized text' => 'uthevet tekst', - 'enter image description here' => 'legg til en bildebeskrivelse her', - 'enter image title here' => 'legg til bildetittel her', - 'enter link description here' => 'legg til linkbeskrivelse her', - 'heading text' => 'overskriftstekst', - 'list text here' => 'listetekst her', - 'quote here' => 'sitat her', - 'strong text' => 'uthevet tekst', -); diff --git a/protected/humhub/modules/ui/messages/nl/markdownEditor.php b/protected/humhub/modules/ui/messages/nl/markdownEditor.php deleted file mode 100644 index f6285781a9..0000000000 --- a/protected/humhub/modules/ui/messages/nl/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Voeg afbeelding/bestand toe', - 'Add link' => 'Voeg link toe', - 'Bold' => 'Vet', - 'Close' => 'Sluiten', - 'Code' => 'Code', - 'Enter a url (e.g. http://example.com)' => 'Voer een URL in (bijvoobeeld http://example.com)', - 'Heading' => 'Kop', - 'Image' => 'Afbeelding', - 'Image/File' => 'Afbeelding/Bestand', - 'Insert Hyperlink' => 'Link invoegen', - 'Insert Image Hyperlink' => 'Afbeelding link invoegen', - 'Italic' => 'Cursief', - 'List' => 'Lijst', - 'Ordered List' => 'Genummerde lijst', - 'Please wait while uploading...' => 'Even geduld aub...', - 'Preview' => 'Voorbeeld', - 'Quote' => 'Quote', - 'Target' => 'Doel', - 'Title' => 'Titel', - 'Title of your link' => 'Titel van de link', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Opsommingslijst', - 'code text here' => 'Code tekst hier', - 'emphasized text' => 'benadrukte tekst', - 'enter image description here' => 'Afbeelding omschrijving', - 'enter image title here' => 'Afbeelding titel', - 'enter link description here' => 'Link omschrijving', - 'heading text' => 'Koptekst', - 'list text here' => 'Lijsttekst', - 'quote here' => 'quote hier plaatsen', - 'strong text' => 'belangrijke tekst', -); diff --git a/protected/humhub/modules/ui/messages/nn-NO/markdownEditor.php b/protected/humhub/modules/ui/messages/nn-NO/markdownEditor.php deleted file mode 100644 index a2e4e72d62..0000000000 --- a/protected/humhub/modules/ui/messages/nn-NO/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'Tittel', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/pl/markdownEditor.php b/protected/humhub/modules/ui/messages/pl/markdownEditor.php deleted file mode 100644 index c5915db3ad..0000000000 --- a/protected/humhub/modules/ui/messages/pl/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Dodaj obrazek/plik', - 'Add link' => 'Dodaj link', - 'Bold' => 'Pogrubienie', - 'Close' => 'Zamknij', - 'Code' => 'Kod', - 'Enter a url (e.g. http://example.com)' => 'Wpisz url (n.p. http://example.com)', - 'Heading' => 'Nagłówek', - 'Image' => 'Obrazek', - 'Image/File' => 'Obrazek/Plik', - 'Insert Hyperlink' => 'Wstaw Hiperłącze', - 'Insert Image Hyperlink' => 'Wstaw link obrazka', - 'Italic' => 'Kursywa', - 'List' => 'Lista', - 'Ordered List' => 'Lista uporządkowana', - 'Please wait while uploading...' => 'Proszę czekać trwa przesyłanie pliku...', - 'Preview' => 'Podgląd', - 'Quote' => 'Cytat', - 'Target' => 'Cel', - 'Title' => 'Tytuł', - 'Title of your link' => 'Tytuł twojego odnośnika', - 'URL/Link' => 'URL/Odnośnik', - 'Unordered List' => 'Lista nieuporządkowana', - 'code text here' => 'tutaj wpisz kod', - 'emphasized text' => 'wyróżniony tekst', - 'enter image description here' => 'wpisz opis obrazka', - 'enter image title here' => 'wpisz tytuł obrazka', - 'enter link description here' => 'wpisz opis odnośnika', - 'heading text' => 'tekst nagłówka', - 'list text here' => 'tutaj wpisz listę', - 'quote here' => 'tutaj wpisz cytat', - 'strong text' => 'pogrubiony tekst', -); diff --git a/protected/humhub/modules/ui/messages/pt-BR/markdownEditor.php b/protected/humhub/modules/ui/messages/pt-BR/markdownEditor.php deleted file mode 100644 index 0895482312..0000000000 --- a/protected/humhub/modules/ui/messages/pt-BR/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Adicionar imagem / arquivo', - 'Add link' => 'Adicionar Link', - 'Bold' => 'Negrito', - 'Close' => 'Fechar', - 'Code' => 'Código', - 'Enter a url (e.g. http://example.com)' => 'Digite uma URL (ex.: http://exemplo.com)', - 'Heading' => 'Título', - 'Image' => 'Imagem', - 'Image/File' => 'Imagem/Arquivo', - 'Insert Hyperlink' => 'Inserir link', - 'Insert Image Hyperlink' => 'Inserir imagem com link', - 'Italic' => 'Itálico', - 'List' => 'Lista', - 'Ordered List' => 'Lista ordenada', - 'Please wait while uploading...' => 'Por favor aguarde enquanto o arquivo é carregado', - 'Preview' => 'Pré-visualizar', - 'Quote' => 'Citar', - 'Target' => 'Alvo', - 'Title' => 'Título', - 'Title of your link' => 'Título do seu link', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Lista não ordenada', - 'code text here' => 'código aqui', - 'emphasized text' => 'texto enfatizado', - 'enter image description here' => 'insira a descrição da imagem aqui', - 'enter image title here' => 'insira o título da imagem aqui', - 'enter link description here' => 'insira a descrição do link aqui', - 'heading text' => 'texto do título', - 'list text here' => 'texto da lista aqui', - 'quote here' => 'citação aqui', - 'strong text' => 'texto em negrito', -); diff --git a/protected/humhub/modules/ui/messages/pt/markdownEditor.php b/protected/humhub/modules/ui/messages/pt/markdownEditor.php deleted file mode 100644 index 4a0a6234f3..0000000000 --- a/protected/humhub/modules/ui/messages/pt/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Adicionar imagem/ficheiro', - 'Add link' => 'Adicionar link', - 'Bold' => 'Negrito', - 'Close' => 'Fechar', - 'Code' => 'Código', - 'Enter a url (e.g. http://example.com)' => 'Introduz um url (ex. http://xkcd.com)', - 'Heading' => 'Cabeçalho', - 'Image' => 'Imagem', - 'Image/File' => 'Imagem/Ficheiro', - 'Insert Hyperlink' => 'Insere uma hiperligação', - 'Insert Image Hyperlink' => 'Insere uma hiperligação de imagem', - 'Italic' => 'Itálico', - 'List' => 'Lista', - 'Ordered List' => 'Lista Ordenada', - 'Please wait while uploading...' => 'Aguarda um pouco enquanto é feito o carregamento...', - 'Preview' => 'Pré-visualizar', - 'Quote' => 'Citar', - 'Target' => 'Alvo', - 'Title' => 'Título', - 'Title of your link' => 'Título do teu link', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Lista Desordenada', - 'code text here' => 'texto do código aqui', - 'emphasized text' => 'texto enfatizado', - 'enter image description here' => 'insere a descrição da imagem aqui', - 'enter image title here' => 'insere o título da imagem aqui', - 'enter link description here' => 'insere a descrição do link aqui', - 'heading text' => 'texto do cabeçalho', - 'list text here' => 'lista de texto aqui', - 'quote here' => 'citar aqui', - 'strong text' => 'texto negrito', -); diff --git a/protected/humhub/modules/ui/messages/ro/markdownEditor.php b/protected/humhub/modules/ui/messages/ro/markdownEditor.php deleted file mode 100644 index bc0ba626c9..0000000000 --- a/protected/humhub/modules/ui/messages/ro/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Adaugă imagine/fișier', - 'Add link' => 'Adaugă legătură', - 'Bold' => 'Îngroșat', - 'Close' => 'Închide', - 'Code' => 'Cod', - 'Enter a url (e.g. http://example.com)' => 'Introdu o legătură (ex: http://exemplu.ro)', - 'Heading' => 'Antet', - 'Image' => 'Imagine', - 'Image/File' => 'Imagine/Fișier', - 'Insert Hyperlink' => 'Inserează Hiperlegătură', - 'Insert Image Hyperlink' => 'Inserează Hiperlegătura Imaginii', - 'Italic' => 'Înclinat', - 'List' => 'Listă', - 'Ordered List' => 'Lista ordonată', - 'Please wait while uploading...' => 'Te rugăm să aștepți până se încarcă...', - 'Preview' => 'Previzualizare', - 'Quote' => 'Citat', - 'Target' => 'Țintă', - 'Title' => 'Titlul', - 'Title of your link' => 'Titlul legăturii tale', - 'URL/Link' => 'URL/Legătură', - 'Unordered List' => 'Listă neordonata', - 'code text here' => 'textul codului aici', - 'emphasized text' => 'text subliniat', - 'enter image description here' => 'introdu descrierea imaginii aici', - 'enter image title here' => 'introdu titlul imaginii aici', - 'enter link description here' => 'introdu descrierea legăturii aici', - 'heading text' => 'textul antetului', - 'list text here' => 'textul listei aici', - 'quote here' => 'citatul aici', - 'strong text' => 'text îngroșat', -); diff --git a/protected/humhub/modules/ui/messages/ru/markdownEditor.php b/protected/humhub/modules/ui/messages/ru/markdownEditor.php deleted file mode 100644 index 4b0c8c5892..0000000000 --- a/protected/humhub/modules/ui/messages/ru/markdownEditor.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Translation: Paul (https://paul.bid) paulbid@protonmail.com - * - */ -return array ( - 'Add image/file' => 'Добавить изображение/файл', - 'Add link' => 'Добавить ссылку', - 'Bold' => 'Полужирный', - 'Close' => 'Закрыть', - 'Code' => 'Блок кода', - 'Enter a url (e.g. http://example.com)' => 'Введите ссылку (например https://example.com)', - 'Heading' => 'Заголовок', - 'Image' => 'Изображение', - 'Image/File' => 'Изображение/Файл', - 'Insert Hyperlink' => 'Вставить гиперссылку', - 'Insert Image Hyperlink' => 'Вставить гиперссылку на изображение', - 'Italic' => 'Курсив', - 'List' => 'Список', - 'Ordered List' => 'Упорядоченный список', - 'Please wait while uploading...' => 'Пожалуйста, подождите пока загрузится...', - 'Preview' => 'Предпросмотр', - 'Quote' => 'Цитата', - 'Target' => 'Цель', - 'Title' => 'Заголовок', - 'Title of your link' => 'Название Вашей ссылки', - 'URL/Link' => 'Адрес/Ссылка', - 'Unordered List' => 'Маркированный список', - 'code text here' => 'вставьте код здесь', - 'emphasized text' => 'выделенный текст', - 'enter image description here' => 'введите описание изображения здесь', - 'enter image title here' => 'введите название изображения здесь', - 'enter link description here' => 'введите описание ссылки здесь', - 'heading text' => 'заголовок текста', - 'list text here' => 'введите список здесь', - 'quote here' => 'процитировать', - 'strong text' => 'выделенный текст', -); diff --git a/protected/humhub/modules/ui/messages/sk/markdownEditor.php b/protected/humhub/modules/ui/messages/sk/markdownEditor.php deleted file mode 100644 index 6328af0daf..0000000000 --- a/protected/humhub/modules/ui/messages/sk/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '사진/파일 추가', - 'Add link' => '링크 추가', - 'Bold' => '굵게', - 'Close' => '닫기', - 'Code' => '소스코드', - 'Enter a url (e.g. http://example.com)' => '링크를 입력하세요 (예: http://google.com)', - 'Heading' => '제목', - 'Image' => '이미지', - 'Image/File' => '이미지/파일', - 'Insert Hyperlink' => '링크 삽입', - 'Insert Image Hyperlink' => '이미지에 링크 삽입', - 'Italic' => '이탤릭', - 'List' => '리스트', - 'Ordered List' => '', - 'Please wait while uploading...' => '업로드 중입니다. 잠시만 기다려주세요...', - 'Preview' => '미리보기', - 'Quote' => '인용', - 'Target' => '타겟', - 'Title' => '제목', - 'Title of your link' => '링크 제목', - 'URL/Link' => 'URL/링크', - 'Unordered List' => '', - 'code text here' => '소스코드를 입력해주세요', - 'emphasized text' => '텍스트 강조', - 'enter image description here' => '이미지 설명을 입력해주세요', - 'enter image title here' => '이미지 제목을 입력해주세요', - 'enter link description here' => '링크 설명을 입력해주세요', - 'heading text' => '제목 텍스트', - 'list text here' => '여기에 리스트 내용을 입력해주세요', - 'quote here' => '여기에 인용할 내용을 입력해주세요', - 'strong text' => '굵은 텍스트', -); diff --git a/protected/humhub/modules/ui/messages/sl/markdownEditor.php b/protected/humhub/modules/ui/messages/sl/markdownEditor.php deleted file mode 100644 index 48a50c3a20..0000000000 --- a/protected/humhub/modules/ui/messages/sl/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'Naslov', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/sq/markdownEditor.php b/protected/humhub/modules/ui/messages/sq/markdownEditor.php deleted file mode 100644 index ca6410a32b..0000000000 --- a/protected/humhub/modules/ui/messages/sq/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'Titulli', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/sv/markdownEditor.php b/protected/humhub/modules/ui/messages/sv/markdownEditor.php deleted file mode 100644 index bc00fc18c5..0000000000 --- a/protected/humhub/modules/ui/messages/sv/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Lägg till bild/fil', - 'Add link' => 'Lägg till länk', - 'Bold' => 'Fet', - 'Close' => 'Stäng', - 'Code' => 'Kod', - 'Enter a url (e.g. http://example.com)' => 'Lägg till url (e.g. http://example.com)', - 'Heading' => 'Rubrikstil', - 'Image' => 'Bild', - 'Image/File' => 'Bild/fil', - 'Insert Hyperlink' => 'Lägg till hyperlänk', - 'Insert Image Hyperlink' => 'Lägg till hyperlänksbild', - 'Italic' => 'Kursiv', - 'List' => 'Lista', - 'Ordered List' => 'Sorterad lista', - 'Please wait while uploading...' => 'Vänligen vänta medan uppladdning sker...', - 'Preview' => 'Förhandsgranska', - 'Quote' => 'Citera', - 'Target' => 'Mål', - 'Title' => 'Titel', - 'Title of your link' => 'Länkrubrik', - 'URL/Link' => 'URL/länk', - 'Unordered List' => 'Punktlista', - 'code text here' => 'Kodtext här', - 'emphasized text' => 'kursiverad text', - 'enter image description here' => 'Lägg till bildbeskrivning', - 'enter image title here' => 'Lägg till bildtitel', - 'enter link description here' => 'Lägg till länkbeskrivning', - 'heading text' => 'Rubriktext', - 'list text here' => 'Listtext här', - 'quote here' => 'citera här', - 'strong text' => 'fettext här', -); diff --git a/protected/humhub/modules/ui/messages/sw/markdownEditor.php b/protected/humhub/modules/ui/messages/sw/markdownEditor.php deleted file mode 100644 index 902d07a9b1..0000000000 --- a/protected/humhub/modules/ui/messages/sw/markdownEditor.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -return [ - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => '', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -]; diff --git a/protected/humhub/modules/ui/messages/th/markdownEditor.php b/protected/humhub/modules/ui/messages/th/markdownEditor.php deleted file mode 100644 index a2f7849920..0000000000 --- a/protected/humhub/modules/ui/messages/th/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'เพิ่มรูปภาพ/ไฟล์', - 'Add link' => 'เพิ่มลิงค์', - 'Bold' => 'ตัวหนา', - 'Close' => 'ปิด', - 'Code' => 'รหัส', - 'Enter a url (e.g. http://example.com)' => 'ป้อน URL (เช่น http://example.com)', - 'Heading' => 'หัวเรื่อง', - 'Image' => 'ภาพ', - 'Image/File' => 'ภาพ/ไฟล์', - 'Insert Hyperlink' => 'แทรกไฮเปอร์ลิงก์', - 'Insert Image Hyperlink' => 'แทรกรูปภาพไฮเปอร์ลิงก์', - 'Italic' => 'ตัวเอียง', - 'List' => 'รายการ', - 'Ordered List' => 'รายการสั่งซื้อ', - 'Please wait while uploading...' => 'กรุณารอสักครู่ขณะอัปโหลด...', - 'Preview' => 'ดูตัวอย่าง', - 'Quote' => 'ใบเสนอราคา', - 'Target' => 'เป้าหมาย', - 'Title' => 'หัวข้อ', - 'Title of your link' => 'ชื่อลิงค์ของคุณ', - 'URL/Link' => 'URL/ลิงค์', - 'Unordered List' => 'รายการที่ไม่เรียงลำดับ', - 'code text here' => 'ข้อความรหัสที่นี่', - 'emphasized text' => 'เน้นข้อความ', - 'enter image description here' => 'ใส่คำอธิบายภาพที่นี่', - 'enter image title here' => 'ใส่ชื่อภาพที่นี่', - 'enter link description here' => 'ใส่คำอธิบายลิงค์ที่นี่', - 'heading text' => 'ข้อความหัวเรื่อง', - 'list text here' => 'รายการข้อความที่นี่', - 'quote here' => 'อ้างที่นี่', - 'strong text' => 'ข้อความที่แข็งแกร่ง', -); diff --git a/protected/humhub/modules/ui/messages/tr/markdownEditor.php b/protected/humhub/modules/ui/messages/tr/markdownEditor.php deleted file mode 100644 index 72e6697408..0000000000 --- a/protected/humhub/modules/ui/messages/tr/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Resim/Dosya Ekle', - 'Add link' => 'Link Ekle', - 'Bold' => 'Kalın', - 'Close' => 'Kapat', - 'Code' => 'Kod', - 'Enter a url (e.g. http://example.com)' => 'Url girin (Örnek: http://example.com)', - 'Heading' => 'Başlık', - 'Image' => 'Resim', - 'Image/File' => 'Resim/Dosya', - 'Insert Hyperlink' => 'Bağlantı Ekle', - 'Insert Image Hyperlink' => 'Resim Bağlantısı Ekle', - 'Italic' => 'İtalik', - 'List' => 'Liste', - 'Ordered List' => 'Düzenlenen Liste', - 'Please wait while uploading...' => 'Yüklenirken lütfen bekleyin...', - 'Preview' => 'Görüntüle', - 'Quote' => 'Alıntı', - 'Target' => 'Hedef', - 'Title' => 'Başlık', - 'Title of your link' => 'Bağlantı Başlığı', - 'URL/Link' => 'URL/Adres', - 'Unordered List' => 'Düzenlenmeyen Liste', - 'code text here' => 'kod metni girin', - 'emphasized text' => 'vurgulanan metin', - 'enter image description here' => 'resim açıklaması girin', - 'enter image title here' => 'resim başlığını girin', - 'enter link description here' => 'bağlantı açıklaması girin', - 'heading text' => 'başlık metni', - 'list text here' => 'metin listesi girin', - 'quote here' => 'alıntı girin', - 'strong text' => 'kalın metin', -); diff --git a/protected/humhub/modules/ui/messages/uk/markdownEditor.php b/protected/humhub/modules/ui/messages/uk/markdownEditor.php deleted file mode 100644 index 738c4b60b2..0000000000 --- a/protected/humhub/modules/ui/messages/uk/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => 'Закрити', - 'Code' => 'Код', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => 'Заголовок', - 'Image' => 'Зображення', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'Заголовок', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/uz/markdownEditor.php b/protected/humhub/modules/ui/messages/uz/markdownEditor.php deleted file mode 100644 index 23c93c53be..0000000000 --- a/protected/humhub/modules/ui/messages/uz/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '', - 'Add link' => '', - 'Bold' => '', - 'Close' => '', - 'Code' => '', - 'Enter a url (e.g. http://example.com)' => '', - 'Heading' => '', - 'Image' => '', - 'Image/File' => '', - 'Insert Hyperlink' => '', - 'Insert Image Hyperlink' => '', - 'Italic' => '', - 'List' => '', - 'Ordered List' => '', - 'Please wait while uploading...' => '', - 'Preview' => '', - 'Quote' => '', - 'Target' => '', - 'Title' => 'Sarlavha', - 'Title of your link' => '', - 'URL/Link' => '', - 'Unordered List' => '', - 'code text here' => '', - 'emphasized text' => '', - 'enter image description here' => '', - 'enter image title here' => '', - 'enter link description here' => '', - 'heading text' => '', - 'list text here' => '', - 'quote here' => '', - 'strong text' => '', -); diff --git a/protected/humhub/modules/ui/messages/vi/markdownEditor.php b/protected/humhub/modules/ui/messages/vi/markdownEditor.php deleted file mode 100644 index aeb3ab2750..0000000000 --- a/protected/humhub/modules/ui/messages/vi/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => 'Thêm image/file', - 'Add link' => 'Thêm link', - 'Bold' => 'In đậm', - 'Close' => 'Đóng', - 'Code' => 'Mã', - 'Enter a url (e.g. http://example.com)' => 'Nhập địa chỉ url (VD: http://vietnam.com)', - 'Heading' => 'Heading', - 'Image' => 'Ảnh', - 'Image/File' => 'Ảnh/Tệp tin', - 'Insert Hyperlink' => 'Chèn liên kết', - 'Insert Image Hyperlink' => 'Chèn liên kết ảnh', - 'Italic' => 'In nghiêng', - 'List' => 'Danh sách', - 'Ordered List' => 'Danh sách có thứ tự', - 'Please wait while uploading...' => 'Vui lòng đợi upload...', - 'Preview' => 'Xem trước', - 'Quote' => 'Trích dẫn', - 'Target' => 'Mục tiêu', - 'Title' => 'Tiêu đề', - 'Title of your link' => 'Tiêu đề liên kết', - 'URL/Link' => 'URL/Link', - 'Unordered List' => 'Danh sách phi thứ tự', - 'code text here' => 'Nhập nội dung Mã ở đây', - 'emphasized text' => 'In đậm chữ', - 'enter image description here' => 'Nhập nội dung mô tả ảnh', - 'enter image title here' => 'Nhập tiêu đề ảnh', - 'enter link description here' => 'Nhập mô tả link', - 'heading text' => 'chữ heading', - 'list text here' => 'Nhập nội dung danh sách ở đây', - 'quote here' => 'Nhập nội dung Trích dẫn ở đây', - 'strong text' => 'bôi đậm chữ', -); diff --git a/protected/humhub/modules/ui/messages/zh-CN/markdownEditor.php b/protected/humhub/modules/ui/messages/zh-CN/markdownEditor.php deleted file mode 100644 index a7de37e161..0000000000 --- a/protected/humhub/modules/ui/messages/zh-CN/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '添加 图片/文件', - 'Add link' => '增加链接', - 'Bold' => '加粗', - 'Close' => '关闭', - 'Code' => '代码', - 'Enter a url (e.g. http://example.com)' => '输入一个url(例如 http://example.com)', - 'Heading' => '标题', - 'Image' => '图片', - 'Image/File' => '图片/文件', - 'Insert Hyperlink' => '插入超链接', - 'Insert Image Hyperlink' => '插入图片超链接', - 'Italic' => '斜体', - 'List' => '列表', - 'Ordered List' => '有序列表', - 'Please wait while uploading...' => '请稍候正在上传中...', - 'Preview' => '预览', - 'Quote' => '引用', - 'Target' => '目标', - 'Title' => '标题', - 'Title of your link' => '链接的标题', - 'URL/Link' => 'URL/链接', - 'Unordered List' => '无序列表', - 'code text here' => '代码在这里', - 'emphasized text' => '强调文本', - 'enter image description here' => '在这里输入图像描述', - 'enter image title here' => '在这里输入图像标题', - 'enter link description here' => '在这里输入链接描述', - 'heading text' => '标题文本', - 'list text here' => '列表在这里', - 'quote here' => '这里引用', - 'strong text' => '加粗文本', -); diff --git a/protected/humhub/modules/ui/messages/zh-TW/markdownEditor.php b/protected/humhub/modules/ui/messages/zh-TW/markdownEditor.php deleted file mode 100644 index c934b067c9..0000000000 --- a/protected/humhub/modules/ui/messages/zh-TW/markdownEditor.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -return array ( - 'Add image/file' => '添加圖像/文件', - 'Add link' => '添加連結', - 'Bold' => '粗體', - 'Close' => '關閉', - 'Code' => '程式碼', - 'Enter a url (e.g. http://example.com)' => '輸入一個網址 (例如: http://example.com)', - 'Heading' => '標題', - 'Image' => '圖像', - 'Image/File' => '圖像/文件', - 'Insert Hyperlink' => '插入超連結', - 'Insert Image Hyperlink' => '插入圖像超連結', - 'Italic' => '斜體', - 'List' => '清單', - 'Ordered List' => '編號清單', - 'Please wait while uploading...' => '上傳中請稍候...', - 'Preview' => '預覽', - 'Quote' => '引用', - 'Target' => '目標框架', - 'Title' => '標題', - 'Title of your link' => '連結的標題', - 'URL/Link' => 'URL網址/連結', - 'Unordered List' => '項目符號清單', - 'code text here' => '在這裡輸入程式碼文字', - 'emphasized text' => '強調文字', - 'enter image description here' => '在這裡輸入圖像說明', - 'enter image title here' => '在這裡輸入圖像標題', - 'enter link description here' => '在這裡輸入連結說明', - 'heading text' => '標題文字', - 'list text here' => '在這裡輸入清單文字', - 'quote here' => '在這裡輸入引用文字', - 'strong text' => '粗體字', -); diff --git a/protected/humhub/modules/user/views/profile/about.php b/protected/humhub/modules/user/views/profile/about.php index dfcd49ff53..b02e3b1a65 100644 --- a/protected/humhub/modules/user/views/profile/about.php +++ b/protected/humhub/modules/user/views/profile/about.php @@ -3,7 +3,6 @@ use humhub\modules\content\widgets\richtext\RichText; use yii\helpers\Html; use humhub\modules\user\models\fieldtype\MarkdownEditor; -use humhub\widgets\MarkdownView; /** * @var $this \humhub\modules\ui\view\components\View diff --git a/protected/humhub/widgets/LayoutAddons.php b/protected/humhub/widgets/LayoutAddons.php index 7358bc77ac..ac4cfa130f 100644 --- a/protected/humhub/widgets/LayoutAddons.php +++ b/protected/humhub/widgets/LayoutAddons.php @@ -10,7 +10,6 @@ namespace humhub\widgets; use humhub\modules\admin\widgets\TrackingWidget; use humhub\modules\tour\widgets\Tour; -use humhub\modules\ui\form\widgets\MarkdownModals; use Yii; /** @@ -41,7 +40,6 @@ class LayoutAddons extends BaseStack if (Yii::$app->params['installed']) { $this->addWidget(BlueimpGallery::class); - $this->addWidget(MarkdownModals::class); if (Yii::$app->params['enablePjax']) { $this->addWidget(PjaxLayoutContent::class); diff --git a/protected/humhub/widgets/MarkdownView.php b/protected/humhub/widgets/MarkdownView.php deleted file mode 100644 index 29eee53295..0000000000 --- a/protected/humhub/widgets/MarkdownView.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php - -/** - * @link https://www.humhub.org/ - * @copyright Copyright (c) 2015 HumHub GmbH & Co. KG - * @license https://www.humhub.com/licences - */ - -namespace humhub\widgets; - -use Exception; - -/** - * MarkdownViewWidget shows Markdown flavored content - * - * @author luke - * @since 0.11 - */ -class MarkdownView extends \yii\base\Widget -{ - - /** - * Markdown to parse - * - * @var string - */ - public $markdown = ""; - - /** - * Markdown parser class - * - * @var string - */ - public $parserClass = "humhub\libs\Markdown"; - - /** - * Purify output after parsing - * - * @var boolean - */ - public $purifyOutput = true; - - /** - * Stylesheet for Highlight.js - */ - public $highlightJsCss = "github"; - - /** - * @var boolean return plain output (do not use widget template) - */ - public $returnPlain = false; - - public function init() - { - if (!\humhub\libs\Helpers::CheckClassType($this->parserClass, "cebe\markdown\Parser")) { - throw new Exception("Invalid markdown parser class given!"); - } - } - - public function run() - { - $this->markdown = \yii\helpers\Html::encode($this->markdown); - - $parserClass = $this->parserClass; - - $parser = new $parserClass; - $html = $parser->parse($this->markdown); - - if ($this->purifyOutput) { - $html = \yii\helpers\HtmlPurifier::process($html, function ($config) { - $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true, 'ftp' => true, 'file' => true]); - $config->getHTMLDefinition(true) - ->addAttribute('a', 'target', 'Text'); - }); - } - - if ($this->returnPlain) { - return $html; - } - - return $this->render('markdownView', ['content' => $html, 'highlightJsCss' => $this->highlightJsCss]); - } - -} diff --git a/protected/humhub/widgets/views/markdownEditor.php b/protected/humhub/widgets/views/markdownEditor.php deleted file mode 100644 index 4549f2b842..0000000000 --- a/protected/humhub/widgets/views/markdownEditor.php +++ /dev/null @@ -1,120 +0,0 @@ -<?php - -use humhub\modules\ui\form\assets\MarkdownFieldAsset; -use yii\helpers\Url; -use yii\helpers\Html; - -/** - * Register BootstrapMarkdown & changes - */ -MarkdownFieldAsset::register($this); - -/** - * Create a hidden field to store uploaded files guids - */ -echo Html::hiddenInput('fileUploaderHiddenGuidField', "", ['id' => 'fileUploaderHiddenGuidField_' . $fieldId]); - -$this->registerJsVar('markdownPreviewUrl', $previewUrl); - -$translations = [ - 'Bold' => Yii::t('UiModule.markdownEditor', 'Bold'), - 'Italic' => Yii::t('UiModule.markdownEditor', 'Italic'), - 'Heading' => Yii::t('UiModule.markdownEditor', 'Heading'), - 'URL/Link' => Yii::t('UiModule.markdownEditor', 'URL/Link'), - 'Image/File' => Yii::t('UiModule.markdownEditor', 'Image/File'), - 'Image' => Yii::t('UiModule.markdownEditor', 'Image'), - 'List' => Yii::t('UiModule.markdownEditor', 'List'), - 'Preview' => Yii::t('UiModule.markdownEditor', 'Preview'), - 'strong text' => Yii::t('UiModule.markdownEditor', 'strong text'), - 'emphasized text' => Yii::t('UiModule.markdownEditor', 'emphasized text'), - 'heading text' => Yii::t('UiModule.markdownEditor', 'heading text'), - 'enter link description here' => Yii::t('UiModule.markdownEditor', 'enter link description here'), - 'Insert Hyperlink' => Yii::t('UiModule.markdownEditor', 'Insert Hyperlink'), - 'enter image description here' => Yii::t('UiModule.markdownEditor', 'enter image description here'), - 'Insert Image Hyperlink' => Yii::t('UiModule.markdownEditor', 'Insert Image Hyperlink'), - 'enter image title here' => Yii::t('UiModule.markdownEditor', 'enter image title here'), - 'list text here' => Yii::t('UiModule.markdownEditor', 'list text here'), - 'Quote' => Yii::t('UiModule.markdownEditor', 'Quote'), - 'quote here' => Yii::t('UiModule.markdownEditor', 'quote here'), - 'Code' => Yii::t('UiModule.markdownEditor', 'Code'), - 'code text here' => Yii::t('UiModule.markdownEditor', 'code text here'), - 'Unordered List' => Yii::t('UiModule.markdownEditor', 'Unordered List'), - 'Ordered List' => Yii::t('UiModule.markdownEditor', 'Ordered List'), -]; - -$translationsJS = "$.fn.markdown.messages['en'] = {\n"; -foreach ($translations as $key => $value) { - $translationsJS .= "\t'" . $key . "': '" . Html::encode($value) . "',\n"; -} -$translationsJS .= "};\n"; -$this->registerJs($translationsJS); -$this->registerJs("initMarkdownEditor('" . $fieldId . "')"); - -?> - -<?php -/** - * We need to use this script part since a markdown editor can also included - * into a modal. So we need to append MarkdownEditors modals later to body. - */ -?> -<script <?= \humhub\libs\Html::nonce() ?> id="markdownEditor_dialogs_<?php echo $fieldId; ?>" type="text/placeholder"> - <div class="modal modal-top" id="addFileModal_<?php echo $fieldId; ?>" tabindex="-1" role="dialog" aria-labelledby="addImageModalLabel" style="z-index:99999" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 class="modal-title" id="addImageModalLabel"><?php echo Yii::t('UiModule.markdownEditor', 'Add image/file'); ?></h4> - </div> - <div class="modal-body"> - - <div class="uploadForm"> - <?php echo Html::beginForm('', 'post'); ?> - <input class="fileUploadButton" type="file" - name="files[]" - data-url="<?php echo Url::to(['/file/file/upload']); ?>" - multiple> - <?php echo Html::endForm(); ?> - </div> - - <div class="uploadProgress"> - <strong><?php echo Yii::t('UiModule.markdownEditor', 'Please wait while uploading...'); ?></strong> - </div> - - - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal"><?php echo Yii::t('UiModule.markdownEditor', 'Close'); ?></button> - </div> - </div> - </div> - </div> - - <div class="modal modal-top" id="addLinkModal_<?php echo $fieldId; ?>" tabindex="-1" role="dialog" style="z-index:99999" aria-labelledby="addLinkModalLabel" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span - aria-hidden="true">×</span></button> - <h4 class="modal-title" id="addLinkModalLabel"><?php echo Yii::t('UiModule.markdownEditor', 'Add link'); ?></h4> - </div> - <div class="modal-body"> - <div class="form-group"> - <label for="addLinkTitle"><?php echo Yii::t('UiModule.markdownEditor', 'Title'); ?></label> - <input type="text" class="form-control linkTitle" - placeholder="<?php echo Yii::t('UiModule.markdownEditor', 'Title of your link'); ?>"> - </div> - <div class="form-group"> - <label for="addLinkTarget"><?php echo Yii::t('UiModule.markdownEditor', 'Target'); ?></label> - <input type="text" class="form-control linkTarget" - placeholder="<?php echo Yii::t('UiModule.markdownEditor', 'Enter a url (e.g. http://example.com)'); ?>"> - </div> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal"><?php echo Yii::t('UiModule.markdownEditor', 'Close'); ?></button> - <button type="button" class="btn btn-primary addLinkButton"><?php echo Yii::t('UiModule.markdownEditor', 'Add link'); ?></button> - </div> - </div> - </div> - </div> -</script> diff --git a/protected/humhub/widgets/views/markdownFieldModals.php b/protected/humhub/widgets/views/markdownFieldModals.php deleted file mode 100644 index 6d7341059c..0000000000 --- a/protected/humhub/widgets/views/markdownFieldModals.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -use humhub\modules\file\widgets\UploadButton; -use humhub\widgets\Button; -use humhub\widgets\ModalButton; -use humhub\widgets\ModalDialog; - -?> - -<div class="modal modal-top" id="markdown-modal-file-upload" tabindex="-1" role="dialog" style="z-index:99999" aria-hidden="true"> - <?php ModalDialog::begin(['header' => Yii::t('UiModule.markdownEditor', 'Add image/file')])?> - <div class="modal-body"> - - <div class="uploadForm"> - <?= UploadButton::widget([ - 'id' => 'markdown-file-upload', - 'label' => true, - 'tooltip' => false, - 'progress' => '#markdown-modal-upload-progress', - 'cssButtonClass' => 'btn-default btn-sm', - 'dropZone' => '#markdown-modal-file-upload', - 'hideInStream' => true - ]) ?> - </div> - - <br> - - <div id="markdown-modal-upload-progress" style="display:none"></div> - - </div> - <div class="modal-footer"> - <?= ModalButton::cancel(Yii::t('base', 'Close')) ?> - </div> - <?php ModalDialog::end() ?> -</div> - -<div class="modal modal-top" id="markdown-modal-add-link" tabindex="-1" role="dialog" style="z-index:99999" aria-hidden="true"> - <?php ModalDialog::begin(['header' => Yii::t('UiModule.markdownEditor', 'Add link')])?> - <div class="modal-body"> - <div class="form-group"> - <label for="addLinkTitle"><?= Yii::t('UiModule.markdownEditor', 'Title'); ?></label> - <input type="text" class="form-control linkTitle" - placeholder="<?= Yii::t('UiModule.markdownEditor', 'Title of your link'); ?>"> - </div> - <div class="form-group"> - <label for="addLinkTarget"><?= Yii::t('UiModule.markdownEditor', 'Target'); ?></label> - <input type="text" class="form-control linkTarget" - placeholder="<?= Yii::t('UiModule.markdownEditor', 'Enter a url (e.g. http://example.com)'); ?>"> - </div> - </div> - <div class="modal-footer"> - <?= ModalButton::cancel(Yii::t('base', 'Close')) ?> - <?= Button::primary(Yii::t('UiModule.markdownEditor', 'Add link'))->cssClass('addLinkButton')->loader(false) ?> - </div> - <?php ModalDialog::end() ?> -</div> diff --git a/protected/humhub/widgets/views/markdownView.php b/protected/humhub/widgets/views/markdownView.php deleted file mode 100644 index ce23da9882..0000000000 --- a/protected/humhub/widgets/views/markdownView.php +++ /dev/null @@ -1,7 +0,0 @@ -<?php -/* @var $content string */ -?> - -<div class="markdown-render"> - <?= $content; ?> -</div> \ No newline at end of file diff --git a/static/css/bootstrap-wysihtml5.css b/static/css/bootstrap-wysihtml5.css deleted file mode 100644 index 44ed777475..0000000000 --- a/static/css/bootstrap-wysihtml5.css +++ /dev/null @@ -1,102 +0,0 @@ -ul.wysihtml5-toolbar { - margin: 0; - padding: 0; - display: block; -} - -ul.wysihtml5-toolbar::after { - clear: both; - display: table; - content: ""; -} - -ul.wysihtml5-toolbar > li { - float: left; - display: list-item; - list-style: none; - margin: 0 5px 10px 0; -} - -ul.wysihtml5-toolbar a[data-wysihtml5-command=bold] { - font-weight: bold; -} - -ul.wysihtml5-toolbar a[data-wysihtml5-command=italic] { - font-style: italic; -} - -ul.wysihtml5-toolbar a[data-wysihtml5-command=underline] { - text-decoration: underline; -} - -ul.wysihtml5-toolbar a.btn.wysihtml5-command-active { - background-image: none; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); - background-color: #E6E6E6; - background-color: #D9D9D9; - outline: 0; -} - -ul.wysihtml5-commands-disabled .dropdown-menu { - display: none !important; -} - -ul.wysihtml5-toolbar div.wysihtml5-colors { - display:block; - width: 50px; - height: 20px; - margin-top: 2px; - margin-left: 5px; - position: absolute; - pointer-events: none; -} - -ul.wysihtml5-toolbar a.wysihtml5-colors-title { - padding-left: 70px; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="black"] { - background: black !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="silver"] { - background: silver !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="gray"] { - background: gray !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="maroon"] { - background: maroon !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="red"] { - background: red !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="purple"] { - background: purple !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="green"] { - background: green !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="olive"] { - background: olive !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="navy"] { - background: navy !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="blue"] { - background: blue !important; -} - -ul.wysihtml5-toolbar div[data-wysihtml5-command-value="orange"] { - background: orange !important; -} diff --git a/static/css/bootstrap3-wysiwyg5-color.css b/static/css/bootstrap3-wysiwyg5-color.css deleted file mode 100644 index 86e7895802..0000000000 --- a/static/css/bootstrap3-wysiwyg5-color.css +++ /dev/null @@ -1,67 +0,0 @@ -.wysiwyg-color-black { - color: black; -} - -.wysiwyg-color-silver { - color: silver; -} - -.wysiwyg-color-gray { - color: gray; -} - -.wysiwyg-color-white { - color: white; -} - -.wysiwyg-color-maroon { - color: maroon; -} - -.wysiwyg-color-red { - color: red; -} - -.wysiwyg-color-purple { - color: purple; -} - -.wysiwyg-color-fuchsia { - color: fuchsia; -} - -.wysiwyg-color-green { - color: green; -} - -.wysiwyg-color-lime { - color: lime; -} - -.wysiwyg-color-olive { - color: olive; -} - -.wysiwyg-color-yellow { - color: yellow; -} - -.wysiwyg-color-navy { - color: navy; -} - -.wysiwyg-color-blue { - color: blue; -} - -.wysiwyg-color-teal { - color: teal; -} - -.wysiwyg-color-aqua { - color: aqua; -} - -.wysiwyg-color-orange { - color: orange; -} \ No newline at end of file diff --git a/static/js/bootstrap3-wysihtml5.js b/static/js/bootstrap3-wysihtml5.js deleted file mode 100644 index e3d2a7450b..0000000000 --- a/static/js/bootstrap3-wysihtml5.js +++ /dev/null @@ -1,521 +0,0 @@ -!function($, wysi) { - "use strict"; - - var tpl = { - "font-styles": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li class='dropdown'>" + - "<a class='btn dropdown-toggle btn-" + size + " btn-default' data-toggle='dropdown' href='#'>" + - "<i class='glyphicon glyphicon-font'></i> <span class='current-font'>" + locale.font_styles.normal + "</span> <b class='caret'></b>" + - "</a>" + - "<ul class='dropdown-menu'>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='div' tabindex='-1'>" + locale.font_styles.normal + "</a></li>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h1' tabindex='-1'>" + locale.font_styles.h1 + "</a></li>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h2' tabindex='-1'>" + locale.font_styles.h2 + "</a></li>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h3' tabindex='-1'>" + locale.font_styles.h3 + "</a></li>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h4'>" + locale.font_styles.h4 + "</a></li>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h5'>" + locale.font_styles.h5 + "</a></li>" + - "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h6'>" + locale.font_styles.h6 + "</a></li>" + - "</ul>" + - "</li>"; - }, - - "emphasis": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li>" + - "<div class='btn-group'>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='bold' title='CTRL+B' tabindex='-1'>" + locale.emphasis.bold + "</a>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='italic' title='CTRL+I' tabindex='-1'>" + locale.emphasis.italic + "</a>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='underline' title='CTRL+U' tabindex='-1'>" + locale.emphasis.underline + "</a>" + - "</div>" + - "</li>"; - }, - - "lists": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li>" + - "<div class='btn-group'>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='insertUnorderedList' title='" + locale.lists.unordered + "' tabindex='-1'><i class='glyphicon glyphicon-list'></i></a>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='insertOrderedList' title='" + locale.lists.ordered + "' tabindex='-1'><i class='glyphicon glyphicon-th-list'></i></a>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='Outdent' title='" + locale.lists.outdent + "' tabindex='-1'><i class='glyphicon glyphicon-indent-right'></i></a>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='Indent' title='" + locale.lists.indent + "' tabindex='-1'><i class='glyphicon glyphicon-indent-left'></i></a>" + - "</div>" + - "</li>"; - }, - - "link": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li>" + - ""+ - "<div class='bootstrap-wysihtml5-insert-link-modal modal fade'>" + - "<div class='modal-dialog'>"+ - "<div class='modal-content'>"+ - "<div class='modal-header'>" + - "<a class='close' data-dismiss='modal'>×</a>" + - "<h4>" + locale.link.insert + "</h4>" + - "</div>" + - "<div class='modal-body'>" + - "<input value='http://' class='bootstrap-wysihtml5-insert-link-url form-control'>" + - "<label class='checkbox'> <input type='checkbox' class='bootstrap-wysihtml5-insert-link-target' checked>" + locale.link.target + "</label>" + - "</div>" + - "<div class='modal-footer'>" + - "<button class='btn btn-default' data-dismiss='modal'>" + locale.link.cancel + "</button>" + - "<button href='#' class='btn btn-primary' data-dismiss='modal'>" + locale.link.insert + "</button>" + - "</div>" + - "</div>" + - "</div>" + - "</div>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='createLink' title='" + locale.link.insert + "' tabindex='-1'><i class='glyphicon glyphicon-share'></i></a>" + - "</li>"; - }, - - "image": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li>" + - "<div class='bootstrap-wysihtml5-insert-image-modal modal fade'>" + - "<div class='modal-dialog'>"+ - "<div class='modal-content'>"+ - "<div class='modal-header'>" + - "<a class='close' data-dismiss='modal'>×</a>" + - "<h4>" + locale.image.insert + "</h4>" + - "</div>" + - "<div class='modal-body'>" + - "<input value='http://' class='bootstrap-wysihtml5-insert-image-url form-control'>" + - "</div>" + - "<div class='modal-footer'>" + - "<button class='btn btn-default' data-dismiss='modal'>" + locale.image.cancel + "</button>" + - "<button class='btn btn-primary' data-dismiss='modal'>" + locale.image.insert + "</button>" + - "</div>" + - "</div>" + - "</div>" + - "</div>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-command='insertImage' title='" + locale.image.insert + "' tabindex='-1'><i class='glyphicon glyphicon-picture'></i></a>" + - "</li>"; - }, - - "html": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li>" + - "<div class='btn-group'>" + - "<a class='btn btn-" + size + " btn-default' data-wysihtml5-action='change_view' title='" + locale.html.edit + "' tabindex='-1'><i class='glyphicon glyphicon-pencil'></i></a>" + - "</div>" + - "</li>"; - }, - - "color": function(locale, options) { - var size = (options && options.size) ? ' btn-'+options.size : ''; - return "<li class='dropdown'>" + - "<a class='btn dropdown-toggle btn-" + size + " btn-default' data-toggle='dropdown' href='#' tabindex='-1'>" + - "<span class='current-color'>" + locale.colours.black + "</span> <b class='caret'></b>" + - "</a>" + - "<ul class='dropdown-menu'>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='black'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='black'>" + locale.colours.black + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='silver'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='silver'>" + locale.colours.silver + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='gray'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='gray'>" + locale.colours.gray + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='maroon'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='maroon'>" + locale.colours.maroon + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='red'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='red'>" + locale.colours.red + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='purple'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='purple'>" + locale.colours.purple + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='green'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='green'>" + locale.colours.green + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='olive'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='olive'>" + locale.colours.olive + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='navy'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='navy'>" + locale.colours.navy + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='blue'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='blue'>" + locale.colours.blue + "</a></li>" + - "<li><div class='wysihtml5-colors' data-wysihtml5-command-value='orange'></div><a class='wysihtml5-colors-title' data-wysihtml5-command='foreColor' data-wysihtml5-command-value='orange'>" + locale.colours.orange + "</a></li>" + - "</ul>" + - "</li>"; - } - }; - - var templates = function(key, locale, options) { - return tpl[key](locale, options); - }; - - - var Wysihtml5 = function(el, options) { - this.el = el; - var toolbarOpts = options || defaultOptions; - for(var t in toolbarOpts.customTemplates) { - tpl[t] = toolbarOpts.customTemplates[t]; - } - this.toolbar = this.createToolbar(el, toolbarOpts); - this.editor = this.createEditor(options); - - window.editor = this.editor; - - $('iframe.wysihtml5-sandbox').each(function(i, el){ - $(el.contentWindow).off('focus.wysihtml5').on({ - 'focus.wysihtml5' : function(){ - $('li.dropdown').removeClass('open'); - } - }); - }); - }; - - Wysihtml5.prototype = { - - constructor: Wysihtml5, - - createEditor: function(options) { - options = options || {}; - - // Add the toolbar to a clone of the options object so multiple instances - // of the WYISYWG don't break because "toolbar" is already defined - options = $.extend(true, {}, options); - options.toolbar = this.toolbar[0]; - - var editor = new wysi.Editor(this.el[0], options); - - if(options && options.events) { - for(var eventName in options.events) { - editor.on(eventName, options.events[eventName]); - } - } - return editor; - }, - - createToolbar: function(el, options) { - var self = this; - var toolbar = $("<ul/>", { - 'class' : "wysihtml5-toolbar", - 'style': "display:none" - }); - var culture = options.locale || defaultOptions.locale || "en"; - for(var key in defaultOptions) { - var value = false; - - if(options[key] !== undefined) { - if(options[key] === true) { - value = true; - } - } else { - value = defaultOptions[key]; - } - - if(value === true) { - toolbar.append(templates(key, locale[culture], options)); - - if(key === "html") { - this.initHtml(toolbar); - } - - if(key === "link") { - this.initInsertLink(toolbar); - } - - if(key === "image") { - this.initInsertImage(toolbar); - } - } - } - - if(options.toolbar) { - for(key in options.toolbar) { - toolbar.append(options.toolbar[key]); - } - } - - toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e) { - var target = e.target || e.srcElement; - var el = $(target); - self.toolbar.find('.current-font').text(el.html()); - }); - - toolbar.find("a[data-wysihtml5-command='foreColor']").click(function(e) { - var target = e.target || e.srcElement; - var el = $(target); - self.toolbar.find('.current-color').text(el.html()); - }); - - this.el.before(toolbar); - - return toolbar; - }, - - initHtml: function(toolbar) { - var changeViewSelector = "a[data-wysihtml5-action='change_view']"; - toolbar.find(changeViewSelector).click(function(e) { - toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled'); - }); - }, - - initInsertImage: function(toolbar) { - var self = this; - var insertImageModal = toolbar.find('.bootstrap-wysihtml5-insert-image-modal'); - var urlInput = insertImageModal.find('.bootstrap-wysihtml5-insert-image-url'); - var insertButton = insertImageModal.find('.btn-primary'); - var initialValue = urlInput.val(); - var caretBookmark; - - var insertImage = function() { - var url = urlInput.val(); - urlInput.val(initialValue); - self.editor.currentView.element.focus(); - if (caretBookmark) { - self.editor.composer.selection.setBookmark(caretBookmark); - caretBookmark = null; - } - self.editor.composer.commands.exec("insertImage", url); - }; - - urlInput.keypress(function(e) { - if(e.which == 13) { - insertImage(); - insertImageModal.modal('hide'); - } - }); - - insertButton.click(insertImage); - - insertImageModal.on('shown', function() { - urlInput.focus(); - }); - - insertImageModal.on('hide', function() { - self.editor.currentView.element.focus(); - }); - - toolbar.find('a[data-wysihtml5-command=insertImage]').click(function() { - var activeButton = $(this).hasClass("wysihtml5-command-active"); - - if (!activeButton) { - self.editor.currentView.element.focus(false); - caretBookmark = self.editor.composer.selection.getBookmark(); - insertImageModal.appendTo('body').modal('show'); - insertImageModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) { - e.stopPropagation(); - }); - return false; - } - else { - return true; - } - }); - }, - - initInsertLink: function(toolbar) { - var self = this; - var insertLinkModal = toolbar.find('.bootstrap-wysihtml5-insert-link-modal'); - var urlInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-url'); - var targetInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-target'); - var insertButton = insertLinkModal.find('.btn-primary'); - var initialValue = urlInput.val(); - var caretBookmark; - - var insertLink = function() { - var url = urlInput.val(); - urlInput.val(initialValue); - self.editor.currentView.element.focus(); - if (caretBookmark) { - self.editor.composer.selection.setBookmark(caretBookmark); - caretBookmark = null; - } - - var newWindow = targetInput.prop("checked"); - self.editor.composer.commands.exec("createLink", { - 'href' : url, - 'target' : (newWindow ? '_blank' : '_self'), - 'rel' : (newWindow ? 'nofollow' : '') - }); - }; - var pressedEnter = false; - - urlInput.keypress(function(e) { - if(e.which == 13) { - insertLink(); - insertLinkModal.modal('hide'); - } - }); - - insertButton.click(insertLink); - - insertLinkModal.on('shown', function() { - urlInput.focus(); - }); - - insertLinkModal.on('hide', function() { - self.editor.currentView.element.focus(); - }); - - toolbar.find('a[data-wysihtml5-command=createLink]').click(function() { - var activeButton = $(this).hasClass("wysihtml5-command-active"); - - if (!activeButton) { - self.editor.currentView.element.focus(false); - caretBookmark = self.editor.composer.selection.getBookmark(); - insertLinkModal.appendTo('body').modal('show'); - insertLinkModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) { - e.stopPropagation(); - }); - return false; - } - else { - return true; - } - }); - } - }; - - // these define our public api - var methods = { - resetDefaults: function() { - $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache); - }, - bypassDefaults: function(options) { - return this.each(function () { - var $this = $(this); - $this.data('wysihtml5', new Wysihtml5($this, options)); - }); - }, - shallowExtend: function (options) { - var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data()); - var that = this; - return methods.bypassDefaults.apply(that, [settings]); - }, - deepExtend: function(options) { - var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {}); - var that = this; - return methods.bypassDefaults.apply(that, [settings]); - }, - init: function(options) { - var that = this; - return methods.shallowExtend.apply(that, [options]); - } - }; - - $.fn.wysihtml5 = function ( method ) { - if ( methods[method] ) { - return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); - } else if ( typeof method === 'object' || ! method ) { - return methods.init.apply( this, arguments ); - } else { - $.error( 'Method ' + method + ' does not exist on jQuery.wysihtml5' ); - } - }; - - $.fn.wysihtml5.Constructor = Wysihtml5; - - var defaultOptions = $.fn.wysihtml5.defaultOptions = { - "font-styles": true, - "color": false, - "emphasis": true, - "lists": true, - "html": false, - "link": true, - "image": true, - "size": 'sm', - events: {}, - parserRules: { - classes: { - // (path_to_project/lib/css/bootstrap3-wysiwyg5-color.css) - "wysiwyg-color-silver" : 1, - "wysiwyg-color-gray" : 1, - "wysiwyg-color-white" : 1, - "wysiwyg-color-maroon" : 1, - "wysiwyg-color-red" : 1, - "wysiwyg-color-purple" : 1, - "wysiwyg-color-fuchsia" : 1, - "wysiwyg-color-green" : 1, - "wysiwyg-color-lime" : 1, - "wysiwyg-color-olive" : 1, - "wysiwyg-color-yellow" : 1, - "wysiwyg-color-navy" : 1, - "wysiwyg-color-blue" : 1, - "wysiwyg-color-teal" : 1, - "wysiwyg-color-aqua" : 1, - "wysiwyg-color-orange" : 1 - }, - tags: { - "b": {}, - "i": {}, - "br": {}, - "ol": {}, - "ul": {}, - "li": {}, - "h1": {}, - "h2": {}, - "h3": {}, - "h4": {}, - "h5": {}, - "h6": {}, - "blockquote": {}, - "u": 1, - "img": { - "check_attributes": { - "width": "numbers", - "alt": "alt", - "src": "url", - "height": "numbers" - } - }, - "a": { - check_attributes: { - 'href': "url", // important to avoid XSS - 'target': 'alt', - 'rel': 'alt' - } - }, - "span": 1, - "div": 1, - // to allow save and edit files with code tag hacks - "code": 1, - "pre": 1 - } - }, - stylesheets: ["./css/bootstrap3-wysiwyg5-color.css"], // (path_to_project/lib/css/bootstrap3-wysiwyg5-color.css) - locale: "en" - }; - - if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') { - $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions); - } - - var locale = $.fn.wysihtml5.locale = { - en: { - font_styles: { - normal: "Normal text", - h1: "Heading 1", - h2: "Heading 2", - h3: "Heading 3", - h4: "Heading 4", - h5: "Heading 5", - h6: "Heading 6" - }, - emphasis: { - bold: "Bold", - italic: "Italic", - underline: "Underline" - }, - lists: { - unordered: "Unordered list", - ordered: "Ordered list", - outdent: "Outdent", - indent: "Indent" - }, - link: { - insert: "Insert link", - cancel: "Cancel", - target: "Open link in new window" - }, - image: { - insert: "Insert image", - cancel: "Cancel" - }, - html: { - edit: "Edit HTML" - }, - colours: { - black: "Black", - silver: "Silver", - gray: "Grey", - maroon: "Maroon", - red: "Red", - purple: "Purple", - green: "Green", - olive: "Olive", - navy: "Navy", - blue: "Blue", - orange: "Orange" - } - } - }; - -}(window.jQuery, window.wysihtml5); diff --git a/static/js/humhub/legacy/markdownEditor.js b/static/js/humhub/legacy/markdownEditor.js deleted file mode 100644 index 906395b5da..0000000000 --- a/static/js/humhub/legacy/markdownEditor.js +++ /dev/null @@ -1,167 +0,0 @@ -// Newly uploaded file -var newFile = ""; - -/** - * @deprecated since 1.5 - */ -function initMarkdownEditor(elementId) { - - if (!$('#addFileModal_' + elementId).length) { - $("body").append($("#markdownEditor_dialogs_" + elementId).html()); - } - - $("#" + elementId).markdown({ - iconlibrary: 'fa', - resize: 'vertical', - additionalButtons: [ - [{ - name: "groupCustom", - data: [{ - name: "cmdLinkWiki", - title: "URL/Link", - icon: {glyph: 'glyphicon glyphicon-link', fa: 'fa fa-link', 'fa-3': 'icon-link'}, - callback: function (e) { - addLinkModal = $('#addLinkModal_' + elementId); - linkTitleField = addLinkModal.find('.linkTitle'); - linkTargetField = addLinkModal.find('.linkTarget'); - - - addLinkModal.find(".close").off('click'); - - - addLinkModal.modal('show'); - - linkTitleField.val(e.getSelection().text); - if (linkTitleField.val() == "") { - linkTitleField.focus(); - } else { - linkTargetField.focus(); - } - - addLinkModal.find('.addLinkButton').off('click'); - addLinkModal.find('.addLinkButton').on('click', function () { - chunk = "[" + linkTitleField.val() + "](" + linkTargetField.val() + ")"; - selected = e.getSelection(), content = e.getContent(), - e.replaceSelection(chunk); - cursor = selected.start; - e.setSelection(cursor, cursor + chunk.length); - addLinkModal.modal('hide') - }); - - addLinkModal.on('hide.bs.modal', function (ee) { - linkTitleField.val(""); - linkTargetField.val(""); - }) - } - }, - { - name: "cmdImgWiki", - title: "Image/File", - icon: {glyph: 'glyphicon glyphicon-picture', fa: 'fa fa-picture-o', 'fa-3': 'icon-picture'}, - callback: function (e) { - - addFileModal = $('#addFileModal_' + elementId); - addFileModal.modal('show'); - addFileModal.find(".uploadForm").show(); - addFileModal.find(".uploadProgress").hide(); - - addFileModal.on('hide.bs.modal', function (ee) { - if (newFile != "") { - var chunk; - if (newFile.mimeType.substring(0, 6) == "image/") { - chunk = "![" + newFile.name + "](file-guid-" + newFile.guid + ") "; - } else { - chunk = "[" + newFile.name + "](file-guid-" + newFile.guid + ") "; - } - insertAtCaret(e.$textarea[0], chunk) - newFile = ""; - } - }) - } - }, - ] - }] - ], - reorderButtonGroups: ["groupFont", "groupCustom", "groupMisc", "groupUtil"], - onPreview: function (e) { - $.ajax({ - type: "POST", - url: markdownPreviewUrl, - data: { - markdown: e.getContent(), - } - }).done(function (previewHtml) { - $('#markdownpreview_' + elementId).html(previewHtml); - }); - var previewContent = "<div id='markdownpreview_" + elementId + "'><div class='loader'></div></div>"; - return previewContent; - } - }); - - $('#addFileModal_' + elementId).find(".uploadProgress").hide(); - $('#addFileModal_' + elementId).find('.fileUploadButton').fileupload({ - dataType: 'json', - done: function (e, data) { - $.each(data.result.files, function (index, file) { - addFileModal = $('#addFileModal_' + elementId); - if (!file.error) { - newFile = file; - hiddenValueField = $('#fileUploaderHiddenGuidField_' + elementId); - hiddenValueField.val(hiddenValueField.val() + "," + file.guid); - addFileModal.modal('hide'); - } else { - alert("file upload error"); - } - }); - }, - progressall: function (e, data) { - newFile = ""; - addFileModal = $('#addFileModal_' + elementId); - - var progress = parseInt(data.loaded / data.total * 100, 10); - addFileModal.find(".uploadForm").hide(); - addFileModal.find(".uploadProgress").show(); - if (progress == 100) { - addFileModal.find(".uploadProgress").hide(); - addFileModal.find(".uploadForm").hide(); - } - } - }).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled'); - -} -function insertAtCaret(txtarea, text) { - if (!txtarea) { - return; - } - - var scrollPos = txtarea.scrollTop; - var strPos = 0; - var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ? "ff" : (document.selection ? "ie" : false)); - if (br == "ie") { - txtarea.focus(); - var range = document.selection.createRange(); - range.moveStart('character', -txtarea.value.length); - strPos = range.text.length; - } else if (br == "ff") { - strPos = txtarea.selectionStart; - } - - var front = (txtarea.value).substring(0, strPos); - var back = (txtarea.value).substring(strPos, txtarea.value.length); - txtarea.value = front + text + back; - strPos = strPos + text.length; - if (br == "ie") { - txtarea.focus(); - var ieRange = document.selection.createRange(); - ieRange.moveStart('character', -txtarea.value.length); - ieRange.moveStart('character', strPos); - ieRange.moveEnd('character', 0); - ieRange.select(); - } else if (br == "ff") { - txtarea.selectionStart = strPos; - txtarea.selectionEnd = strPos; - txtarea.focus(); - } - - txtarea.scrollTop = scrollPos; -} diff --git a/static/js/wysihtml5-0.3.0.js b/static/js/wysihtml5-0.3.0.js deleted file mode 100644 index d96dc69e40..0000000000 --- a/static/js/wysihtml5-0.3.0.js +++ /dev/null @@ -1,9523 +0,0 @@ -/** - * @license wysihtml5 v0.3.0 - * https://github.com/xing/wysihtml5 - * - * Author: Christopher Blum (https://github.com/tiff) - * - * Copyright (C) 2012 XING AG - * Licensed under the MIT license (MIT) - * - */ -var wysihtml5 = { - version: "0.3.0", - - // namespaces - commands: {}, - dom: {}, - quirks: {}, - toolbar: {}, - lang: {}, - selection: {}, - views: {}, - - INVISIBLE_SPACE: "\uFEFF", - - EMPTY_FUNCTION: function() {}, - - ELEMENT_NODE: 1, - TEXT_NODE: 3, - - BACKSPACE_KEY: 8, - ENTER_KEY: 13, - ESCAPE_KEY: 27, - SPACE_KEY: 32, - DELETE_KEY: 46 -};/** - * @license Rangy, a cross-browser JavaScript range and selection library - * http://code.google.com/p/rangy/ - * - * Copyright 2011, Tim Down - * Licensed under the MIT license. - * Version: 1.2.2 - * Build date: 13 November 2011 - */ -window['rangy'] = (function() { - - - var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; - - var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"]; - - var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", - "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", - "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; - - var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; - - // Subset of TextRange's full set of methods that we're interested in - var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", - "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"]; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Trio of functions taken from Peter Michaux's article: - // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting - function isHostMethod(o, p) { - var t = typeof o[p]; - return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; - } - - function isHostObject(o, p) { - return !!(typeof o[p] == OBJECT && o[p]); - } - - function isHostProperty(o, p) { - return typeof o[p] != UNDEFINED; - } - - // Creates a convenience function to save verbose repeated calls to tests functions - function createMultiplePropertyTest(testFunc) { - return function(o, props) { - var i = props.length; - while (i--) { - if (!testFunc(o, props[i])) { - return false; - } - } - return true; - }; - } - - // Next trio of functions are a convenience to save verbose repeated calls to previous two functions - var areHostMethods = createMultiplePropertyTest(isHostMethod); - var areHostObjects = createMultiplePropertyTest(isHostObject); - var areHostProperties = createMultiplePropertyTest(isHostProperty); - - function isTextRange(range) { - return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); - } - - var api = { - version: "1.2.2", - initialized: false, - supported: true, - - util: { - isHostMethod: isHostMethod, - isHostObject: isHostObject, - isHostProperty: isHostProperty, - areHostMethods: areHostMethods, - areHostObjects: areHostObjects, - areHostProperties: areHostProperties, - isTextRange: isTextRange - }, - - features: {}, - - modules: {}, - config: { - alertOnWarn: false, - preferTextRange: false - } - }; - - function fail(reason) { - window.alert("Rangy not supported in your browser. Reason: " + reason); - api.initialized = true; - api.supported = false; - } - - api.fail = fail; - - function warn(msg) { - var warningMessage = "Rangy warning: " + msg; - if (api.config.alertOnWarn) { - window.alert(warningMessage); - } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) { - window.console.log(warningMessage); - } - } - - api.warn = warn; - - if ({}.hasOwnProperty) { - api.util.extend = function(o, props) { - for (var i in props) { - if (props.hasOwnProperty(i)) { - o[i] = props[i]; - } - } - }; - } else { - fail("hasOwnProperty not supported"); - } - - var initListeners = []; - var moduleInitializers = []; - - // Initialization - function init() { - if (api.initialized) { - return; - } - var testRange; - var implementsDomRange = false, implementsTextRange = false; - - // First, perform basic feature tests - - if (isHostMethod(document, "createRange")) { - testRange = document.createRange(); - if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { - implementsDomRange = true; - } - testRange.detach(); - } - - var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0]; - - if (body && isHostMethod(body, "createTextRange")) { - testRange = body.createTextRange(); - if (isTextRange(testRange)) { - implementsTextRange = true; - } - } - - if (!implementsDomRange && !implementsTextRange) { - fail("Neither Range nor TextRange are implemented"); - } - - api.initialized = true; - api.features = { - implementsDomRange: implementsDomRange, - implementsTextRange: implementsTextRange - }; - - // Initialize modules and call init listeners - var allListeners = moduleInitializers.concat(initListeners); - for (var i = 0, len = allListeners.length; i < len; ++i) { - try { - allListeners[i](api); - } catch (ex) { - if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { - window.console.log("Init listener threw an exception. Continuing.", ex); - } - - } - } - } - - // Allow external scripts to initialize this library in case it's loaded after the document has loaded - api.init = init; - - // Execute listener immediately if already initialized - api.addInitListener = function(listener) { - if (api.initialized) { - listener(api); - } else { - initListeners.push(listener); - } - }; - - var createMissingNativeApiListeners = []; - - api.addCreateMissingNativeApiListener = function(listener) { - createMissingNativeApiListeners.push(listener); - }; - - function createMissingNativeApi(win) { - win = win || window; - init(); - - // Notify listeners - for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { - createMissingNativeApiListeners[i](win); - } - } - - api.createMissingNativeApi = createMissingNativeApi; - - /** - * @constructor - */ - function Module(name) { - this.name = name; - this.initialized = false; - this.supported = false; - } - - Module.prototype.fail = function(reason) { - this.initialized = true; - this.supported = false; - - throw new Error("Module '" + this.name + "' failed to load: " + reason); - }; - - Module.prototype.warn = function(msg) { - api.warn("Module " + this.name + ": " + msg); - }; - - Module.prototype.createError = function(msg) { - return new Error("Error in Rangy " + this.name + " module: " + msg); - }; - - api.createModule = function(name, initFunc) { - var module = new Module(name); - api.modules[name] = module; - - moduleInitializers.push(function(api) { - initFunc(api, module); - module.initialized = true; - module.supported = true; - }); - }; - - api.requireModules = function(modules) { - for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) { - moduleName = modules[i]; - module = api.modules[moduleName]; - if (!module || !(module instanceof Module)) { - throw new Error("Module '" + moduleName + "' not found"); - } - if (!module.supported) { - throw new Error("Module '" + moduleName + "' not supported"); - } - } - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Wait for document to load before running tests - - var docReady = false; - - var loadHandler = function(e) { - - if (!docReady) { - docReady = true; - if (!api.initialized) { - init(); - } - } - }; - - // Test whether we have window and document objects that we will need - if (typeof window == UNDEFINED) { - fail("No window found"); - return; - } - if (typeof document == UNDEFINED) { - fail("No document found"); - return; - } - - if (isHostMethod(document, "addEventListener")) { - document.addEventListener("DOMContentLoaded", loadHandler, false); - } - - // Add a fallback in case the DOMContentLoaded event isn't supported - if (isHostMethod(window, "addEventListener")) { - window.addEventListener("load", loadHandler, false); - } else if (isHostMethod(window, "attachEvent")) { - window.attachEvent("onload", loadHandler); - } else { - fail("Window does not have required addEventListener or attachEvent method"); - } - - return api; -})(); -rangy.createModule("DomUtil", function(api, module) { - - var UNDEF = "undefined"; - var util = api.util; - - // Perform feature tests - if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { - module.fail("document missing a Node creation method"); - } - - if (!util.isHostMethod(document, "getElementsByTagName")) { - module.fail("document missing getElementsByTagName method"); - } - - var el = document.createElement("div"); - if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || - !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { - module.fail("Incomplete Element implementation"); - } - - // innerHTML is required for Range's createContextualFragment method - if (!util.isHostProperty(el, "innerHTML")) { - module.fail("Element is missing innerHTML property"); - } - - var textNode = document.createTextNode("test"); - if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || - !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || - !util.areHostProperties(textNode, ["data"]))) { - module.fail("Incomplete Text Node implementation"); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been - // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that - // contains just the document as a single element and the value searched for is the document. - var arrayContains = /*Array.prototype.indexOf ? - function(arr, val) { - return arr.indexOf(val) > -1; - }:*/ - - function(arr, val) { - var i = arr.length; - while (i--) { - if (arr[i] === val) { - return true; - } - } - return false; - }; - - // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI - function isHtmlNamespace(node) { - var ns; - return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); - } - - function parentElement(node) { - var parent = node.parentNode; - return (parent.nodeType == 1) ? parent : null; - } - - function getNodeIndex(node) { - var i = 0; - while( (node = node.previousSibling) ) { - i++; - } - return i; - } - - function getNodeLength(node) { - var childNodes; - return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0); - } - - function getCommonAncestor(node1, node2) { - var ancestors = [], n; - for (n = node1; n; n = n.parentNode) { - ancestors.push(n); - } - - for (n = node2; n; n = n.parentNode) { - if (arrayContains(ancestors, n)) { - return n; - } - } - - return null; - } - - function isAncestorOf(ancestor, descendant, selfIsAncestor) { - var n = selfIsAncestor ? descendant : descendant.parentNode; - while (n) { - if (n === ancestor) { - return true; - } else { - n = n.parentNode; - } - } - return false; - } - - function getClosestAncestorIn(node, ancestor, selfIsAncestor) { - var p, n = selfIsAncestor ? node : node.parentNode; - while (n) { - p = n.parentNode; - if (p === ancestor) { - return n; - } - n = p; - } - return null; - } - - function isCharacterDataNode(node) { - var t = node.nodeType; - return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment - } - - function insertAfter(node, precedingNode) { - var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; - if (nextNode) { - parent.insertBefore(node, nextNode); - } else { - parent.appendChild(node); - } - return node; - } - - // Note that we cannot use splitText() because it is bugridden in IE 9. - function splitDataNode(node, index) { - var newNode = node.cloneNode(false); - newNode.deleteData(0, index); - node.deleteData(index, node.length - index); - insertAfter(newNode, node); - return newNode; - } - - function getDocument(node) { - if (node.nodeType == 9) { - return node; - } else if (typeof node.ownerDocument != UNDEF) { - return node.ownerDocument; - } else if (typeof node.document != UNDEF) { - return node.document; - } else if (node.parentNode) { - return getDocument(node.parentNode); - } else { - throw new Error("getDocument: no document found for node"); - } - } - - function getWindow(node) { - var doc = getDocument(node); - if (typeof doc.defaultView != UNDEF) { - return doc.defaultView; - } else if (typeof doc.parentWindow != UNDEF) { - return doc.parentWindow; - } else { - throw new Error("Cannot get a window object for node"); - } - } - - function getIframeDocument(iframeEl) { - if (typeof iframeEl.contentDocument != UNDEF) { - return iframeEl.contentDocument; - } else if (typeof iframeEl.contentWindow != UNDEF) { - return iframeEl.contentWindow.document; - } else { - throw new Error("getIframeWindow: No Document object found for iframe element"); - } - } - - function getIframeWindow(iframeEl) { - if (typeof iframeEl.contentWindow != UNDEF) { - return iframeEl.contentWindow; - } else if (typeof iframeEl.contentDocument != UNDEF) { - return iframeEl.contentDocument.defaultView; - } else { - throw new Error("getIframeWindow: No Window object found for iframe element"); - } - } - - function getBody(doc) { - return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; - } - - function getRootContainer(node) { - var parent; - while ( (parent = node.parentNode) ) { - node = parent; - } - return node; - } - - function comparePoints(nodeA, offsetA, nodeB, offsetB) { - // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing - var nodeC, root, childA, childB, n; - if (nodeA == nodeB) { - - // Case 1: nodes are the same - return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; - } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { - - // Case 2: node C (container B or an ancestor) is a child node of A - return offsetA <= getNodeIndex(nodeC) ? -1 : 1; - } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { - - // Case 3: node C (container A or an ancestor) is a child node of B - return getNodeIndex(nodeC) < offsetB ? -1 : 1; - } else { - - // Case 4: containers are siblings or descendants of siblings - root = getCommonAncestor(nodeA, nodeB); - childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); - childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); - - if (childA === childB) { - // This shouldn't be possible - - throw new Error("comparePoints got to case 4 and childA and childB are the same!"); - } else { - n = root.firstChild; - while (n) { - if (n === childA) { - return -1; - } else if (n === childB) { - return 1; - } - n = n.nextSibling; - } - throw new Error("Should not be here!"); - } - } - } - - function fragmentFromNodeChildren(node) { - var fragment = getDocument(node).createDocumentFragment(), child; - while ( (child = node.firstChild) ) { - fragment.appendChild(child); - } - return fragment; - } - - function inspectNode(node) { - if (!node) { - return "[No node]"; - } - if (isCharacterDataNode(node)) { - return '"' + node.data + '"'; - } else if (node.nodeType == 1) { - var idAttr = node.id ? ' id="' + node.id + '"' : ""; - return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]"; - } else { - return node.nodeName; - } - } - - /** - * @constructor - */ - function NodeIterator(root) { - this.root = root; - this._next = root; - } - - NodeIterator.prototype = { - _current: null, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - var n = this._current = this._next; - var child, next; - if (this._current) { - child = n.firstChild; - if (child) { - this._next = child; - } else { - next = null; - while ((n !== this.root) && !(next = n.nextSibling)) { - n = n.parentNode; - } - this._next = next; - } - } - return this._current; - }, - - detach: function() { - this._current = this._next = this.root = null; - } - }; - - function createIterator(root) { - return new NodeIterator(root); - } - - /** - * @constructor - */ - function DomPosition(node, offset) { - this.node = node; - this.offset = offset; - } - - DomPosition.prototype = { - equals: function(pos) { - return this.node === pos.node & this.offset == pos.offset; - }, - - inspect: function() { - return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; - } - }; - - /** - * @constructor - */ - function DOMException(codeName) { - this.code = this[codeName]; - this.codeName = codeName; - this.message = "DOMException: " + this.codeName; - } - - DOMException.prototype = { - INDEX_SIZE_ERR: 1, - HIERARCHY_REQUEST_ERR: 3, - WRONG_DOCUMENT_ERR: 4, - NO_MODIFICATION_ALLOWED_ERR: 7, - NOT_FOUND_ERR: 8, - NOT_SUPPORTED_ERR: 9, - INVALID_STATE_ERR: 11 - }; - - DOMException.prototype.toString = function() { - return this.message; - }; - - api.dom = { - arrayContains: arrayContains, - isHtmlNamespace: isHtmlNamespace, - parentElement: parentElement, - getNodeIndex: getNodeIndex, - getNodeLength: getNodeLength, - getCommonAncestor: getCommonAncestor, - isAncestorOf: isAncestorOf, - getClosestAncestorIn: getClosestAncestorIn, - isCharacterDataNode: isCharacterDataNode, - insertAfter: insertAfter, - splitDataNode: splitDataNode, - getDocument: getDocument, - getWindow: getWindow, - getIframeWindow: getIframeWindow, - getIframeDocument: getIframeDocument, - getBody: getBody, - getRootContainer: getRootContainer, - comparePoints: comparePoints, - inspectNode: inspectNode, - fragmentFromNodeChildren: fragmentFromNodeChildren, - createIterator: createIterator, - DomPosition: DomPosition - }; - - api.DOMException = DOMException; -});rangy.createModule("DomRange", function(api, module) { - api.requireModules( ["DomUtil"] ); - - - var dom = api.dom; - var DomPosition = dom.DomPosition; - var DOMException = api.DOMException; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Utility functions - - function isNonTextPartiallySelected(node, range) { - return (node.nodeType != 3) && - (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true)); - } - - function getRangeDocument(range) { - return dom.getDocument(range.startContainer); - } - - function dispatchEvent(range, type, args) { - var listeners = range._listeners[type]; - if (listeners) { - for (var i = 0, len = listeners.length; i < len; ++i) { - listeners[i].call(range, {target: range, args: args}); - } - } - } - - function getBoundaryBeforeNode(node) { - return new DomPosition(node.parentNode, dom.getNodeIndex(node)); - } - - function getBoundaryAfterNode(node) { - return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1); - } - - function insertNodeAtPosition(node, n, o) { - var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; - if (dom.isCharacterDataNode(n)) { - if (o == n.length) { - dom.insertAfter(node, n); - } else { - n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o)); - } - } else if (o >= n.childNodes.length) { - n.appendChild(node); - } else { - n.insertBefore(node, n.childNodes[o]); - } - return firstNodeInserted; - } - - function cloneSubtree(iterator) { - var partiallySelected; - for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { - partiallySelected = iterator.isPartiallySelectedSubtree(); - - node = node.cloneNode(!partiallySelected); - if (partiallySelected) { - subIterator = iterator.getSubtreeIterator(); - node.appendChild(cloneSubtree(subIterator)); - subIterator.detach(true); - } - - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function iterateSubtree(rangeIterator, func, iteratorState) { - var it, n; - iteratorState = iteratorState || { stop: false }; - for (var node, subRangeIterator; node = rangeIterator.next(); ) { - //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node)); - if (rangeIterator.isPartiallySelectedSubtree()) { - // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the - // node selected by the Range. - if (func(node) === false) { - iteratorState.stop = true; - return; - } else { - subRangeIterator = rangeIterator.getSubtreeIterator(); - iterateSubtree(subRangeIterator, func, iteratorState); - subRangeIterator.detach(true); - if (iteratorState.stop) { - return; - } - } - } else { - // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its - // descendant - it = dom.createIterator(node); - while ( (n = it.next()) ) { - if (func(n) === false) { - iteratorState.stop = true; - return; - } - } - } - } - } - - function deleteSubtree(iterator) { - var subIterator; - while (iterator.next()) { - if (iterator.isPartiallySelectedSubtree()) { - subIterator = iterator.getSubtreeIterator(); - deleteSubtree(subIterator); - subIterator.detach(true); - } else { - iterator.remove(); - } - } - } - - function extractSubtree(iterator) { - - for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { - - - if (iterator.isPartiallySelectedSubtree()) { - node = node.cloneNode(false); - subIterator = iterator.getSubtreeIterator(); - node.appendChild(extractSubtree(subIterator)); - subIterator.detach(true); - } else { - iterator.remove(); - } - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function getNodesInRange(range, nodeTypes, filter) { - //log.info("getNodesInRange, " + nodeTypes.join(",")); - var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; - var filterExists = !!filter; - if (filterNodeTypes) { - regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); - } - - var nodes = []; - iterateSubtree(new RangeIterator(range, false), function(node) { - if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { - nodes.push(node); - } - }); - return nodes; - } - - function inspect(range) { - var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); - return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + - dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) - - /** - * @constructor - */ - function RangeIterator(range, clonePartiallySelectedTextNodes) { - this.range = range; - this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; - - - - if (!range.collapsed) { - this.sc = range.startContainer; - this.so = range.startOffset; - this.ec = range.endContainer; - this.eo = range.endOffset; - var root = range.commonAncestorContainer; - - if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) { - this.isSingleCharacterDataNode = true; - this._first = this._last = this._next = this.sc; - } else { - this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? - this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true); - this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? - this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true); - } - - } - } - - RangeIterator.prototype = { - _current: null, - _next: null, - _first: null, - _last: null, - isSingleCharacterDataNode: false, - - reset: function() { - this._current = null; - this._next = this._first; - }, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - // Move to next node - var current = this._current = this._next; - if (current) { - this._next = (current !== this._last) ? current.nextSibling : null; - - // Check for partially selected text nodes - if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { - if (current === this.ec) { - - (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); - } - if (this._current === this.sc) { - - (current = current.cloneNode(true)).deleteData(0, this.so); - } - } - } - - return current; - }, - - remove: function() { - var current = this._current, start, end; - - if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { - start = (current === this.sc) ? this.so : 0; - end = (current === this.ec) ? this.eo : current.length; - if (start != end) { - current.deleteData(start, end - start); - } - } else { - if (current.parentNode) { - current.parentNode.removeChild(current); - } else { - - } - } - }, - - // Checks if the current node is partially selected - isPartiallySelectedSubtree: function() { - var current = this._current; - return isNonTextPartiallySelected(current, this.range); - }, - - getSubtreeIterator: function() { - var subRange; - if (this.isSingleCharacterDataNode) { - subRange = this.range.cloneRange(); - subRange.collapse(); - } else { - subRange = new Range(getRangeDocument(this.range)); - var current = this._current; - var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current); - - if (dom.isAncestorOf(current, this.sc, true)) { - startContainer = this.sc; - startOffset = this.so; - } - if (dom.isAncestorOf(current, this.ec, true)) { - endContainer = this.ec; - endOffset = this.eo; - } - - updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); - } - return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); - }, - - detach: function(detachRange) { - if (detachRange) { - this.range.detach(); - } - this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; - } - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Exceptions - - /** - * @constructor - */ - function RangeException(codeName) { - this.code = this[codeName]; - this.codeName = codeName; - this.message = "RangeException: " + this.codeName; - } - - RangeException.prototype = { - BAD_BOUNDARYPOINTS_ERR: 1, - INVALID_NODE_TYPE_ERR: 2 - }; - - RangeException.prototype.toString = function() { - return this.message; - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - /** - * Currently iterates through all nodes in the range on creation until I think of a decent way to do it - * TODO: Look into making this a proper iterator, not requiring preloading everything first - * @constructor - */ - function RangeNodeIterator(range, nodeTypes, filter) { - this.nodes = getNodesInRange(range, nodeTypes, filter); - this._next = this.nodes[0]; - this._position = 0; - } - - RangeNodeIterator.prototype = { - _current: null, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - this._current = this._next; - this._next = this.nodes[ ++this._position ]; - return this._current; - }, - - detach: function() { - this._current = this._next = this.nodes = null; - } - }; - - var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; - var rootContainerNodeTypes = [2, 9, 11]; - var readonlyNodeTypes = [5, 6, 10, 12]; - var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; - var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; - - function createAncestorFinder(nodeTypes) { - return function(node, selfIsAncestor) { - var t, n = selfIsAncestor ? node : node.parentNode; - while (n) { - t = n.nodeType; - if (dom.arrayContains(nodeTypes, t)) { - return n; - } - n = n.parentNode; - } - return null; - }; - } - - var getRootContainer = dom.getRootContainer; - var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); - var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); - var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); - - function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { - if (getDocTypeNotationEntityAncestor(node, allowSelf)) { - throw new RangeException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertNotDetached(range) { - if (!range.startContainer) { - throw new DOMException("INVALID_STATE_ERR"); - } - } - - function assertValidNodeType(node, invalidTypes) { - if (!dom.arrayContains(invalidTypes, node.nodeType)) { - throw new RangeException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertValidOffset(node, offset) { - if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) { - throw new DOMException("INDEX_SIZE_ERR"); - } - } - - function assertSameDocumentOrFragment(node1, node2) { - if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - } - - function assertNodeNotReadOnly(node) { - if (getReadonlyAncestor(node, true)) { - throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); - } - } - - function assertNode(node, codeName) { - if (!node) { - throw new DOMException(codeName); - } - } - - function isOrphan(node) { - return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); - } - - function isValidOffset(node, offset) { - return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length); - } - - function assertRangeValid(range) { - assertNotDetached(range); - if (isOrphan(range.startContainer) || isOrphan(range.endContainer) || - !isValidOffset(range.startContainer, range.startOffset) || - !isValidOffset(range.endContainer, range.endOffset)) { - throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); - } - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Test the browser's innerHTML support to decide how to implement createContextualFragment - var styleEl = document.createElement("style"); - var htmlParsingConforms = false; - try { - styleEl.innerHTML = "<b>x</b>"; - htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node - } catch (e) { - // IE 6 and 7 throw - } - - api.features.htmlParsingConforms = htmlParsingConforms; - - var createContextualFragment = htmlParsingConforms ? - - // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See - // discussion and base code for this implementation at issue 67. - // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface - // Thanks to Aleks Williams. - function(fragmentStr) { - // "Let node the context object's start's node." - var node = this.startContainer; - var doc = dom.getDocument(node); - - // "If the context object's start's node is null, raise an INVALID_STATE_ERR - // exception and abort these steps." - if (!node) { - throw new DOMException("INVALID_STATE_ERR"); - } - - // "Let element be as follows, depending on node's interface:" - // Document, Document Fragment: null - var el = null; - - // "Element: node" - if (node.nodeType == 1) { - el = node; - - // "Text, Comment: node's parentElement" - } else if (dom.isCharacterDataNode(node)) { - el = dom.parentElement(node); - } - - // "If either element is null or element's ownerDocument is an HTML document - // and element's local name is "html" and element's namespace is the HTML - // namespace" - if (el === null || ( - el.nodeName == "HTML" - && dom.isHtmlNamespace(dom.getDocument(el).documentElement) - && dom.isHtmlNamespace(el) - )) { - - // "let element be a new Element with "body" as its local name and the HTML - // namespace as its namespace."" - el = doc.createElement("body"); - } else { - el = el.cloneNode(false); - } - - // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." - // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." - // "In either case, the algorithm must be invoked with fragment as the input - // and element as the context element." - el.innerHTML = fragmentStr; - - // "If this raises an exception, then abort these steps. Otherwise, let new - // children be the nodes returned." - - // "Let fragment be a new DocumentFragment." - // "Append all new children to fragment." - // "Return fragment." - return dom.fragmentFromNodeChildren(el); - } : - - // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that - // previous versions of Rangy used (with the exception of using a body element rather than a div) - function(fragmentStr) { - assertNotDetached(this); - var doc = getRangeDocument(this); - var el = doc.createElement("body"); - el.innerHTML = fragmentStr; - - return dom.fragmentFromNodeChildren(el); - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer"]; - - var s2s = 0, s2e = 1, e2e = 2, e2s = 3; - var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; - - function RangePrototype() {} - - RangePrototype.prototype = { - attachListener: function(type, listener) { - this._listeners[type].push(listener); - }, - - compareBoundaryPoints: function(how, range) { - assertRangeValid(this); - assertSameDocumentOrFragment(this.startContainer, range.startContainer); - - var nodeA, offsetA, nodeB, offsetB; - var prefixA = (how == e2s || how == s2s) ? "start" : "end"; - var prefixB = (how == s2e || how == s2s) ? "start" : "end"; - nodeA = this[prefixA + "Container"]; - offsetA = this[prefixA + "Offset"]; - nodeB = range[prefixB + "Container"]; - offsetB = range[prefixB + "Offset"]; - return dom.comparePoints(nodeA, offsetA, nodeB, offsetB); - }, - - insertNode: function(node) { - assertRangeValid(this); - assertValidNodeType(node, insertableNodeTypes); - assertNodeNotReadOnly(this.startContainer); - - if (dom.isAncestorOf(node, this.startContainer, true)) { - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - - // No check for whether the container of the start of the Range is of a type that does not allow - // children of the type of node: the browser's DOM implementation should do this for us when we attempt - // to add the node - - var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); - this.setStartBefore(firstNodeInserted); - }, - - cloneContents: function() { - assertRangeValid(this); - - var clone, frag; - if (this.collapsed) { - return getRangeDocument(this).createDocumentFragment(); - } else { - if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) { - clone = this.startContainer.cloneNode(true); - clone.data = clone.data.slice(this.startOffset, this.endOffset); - frag = getRangeDocument(this).createDocumentFragment(); - frag.appendChild(clone); - return frag; - } else { - var iterator = new RangeIterator(this, true); - clone = cloneSubtree(iterator); - iterator.detach(); - } - return clone; - } - }, - - canSurroundContents: function() { - assertRangeValid(this); - assertNodeNotReadOnly(this.startContainer); - assertNodeNotReadOnly(this.endContainer); - - // Check if the contents can be surrounded. Specifically, this means whether the range partially selects - // no non-text nodes. - var iterator = new RangeIterator(this, true); - var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || - (iterator._last && isNonTextPartiallySelected(iterator._last, this))); - iterator.detach(); - return !boundariesInvalid; - }, - - surroundContents: function(node) { - assertValidNodeType(node, surroundNodeTypes); - - if (!this.canSurroundContents()) { - throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); - } - - // Extract the contents - var content = this.extractContents(); - - // Clear the children of the node - if (node.hasChildNodes()) { - while (node.lastChild) { - node.removeChild(node.lastChild); - } - } - - // Insert the new node and add the extracted contents - insertNodeAtPosition(node, this.startContainer, this.startOffset); - node.appendChild(content); - - this.selectNode(node); - }, - - cloneRange: function() { - assertRangeValid(this); - var range = new Range(getRangeDocument(this)); - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = this[prop]; - } - return range; - }, - - toString: function() { - assertRangeValid(this); - var sc = this.startContainer; - if (sc === this.endContainer && dom.isCharacterDataNode(sc)) { - return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; - } else { - var textBits = [], iterator = new RangeIterator(this, true); - - iterateSubtree(iterator, function(node) { - // Accept only text or CDATA nodes, not comments - - if (node.nodeType == 3 || node.nodeType == 4) { - textBits.push(node.data); - } - }); - iterator.detach(); - return textBits.join(""); - } - }, - - // The methods below are all non-standard. The following batch were introduced by Mozilla but have since - // been removed from Mozilla. - - compareNode: function(node) { - assertRangeValid(this); - - var parent = node.parentNode; - var nodeIndex = dom.getNodeIndex(node); - - if (!parent) { - throw new DOMException("NOT_FOUND_ERR"); - } - - var startComparison = this.comparePoint(parent, nodeIndex), - endComparison = this.comparePoint(parent, nodeIndex + 1); - - if (startComparison < 0) { // Node starts before - return (endComparison > 0) ? n_b_a : n_b; - } else { - return (endComparison > 0) ? n_a : n_i; - } - }, - - comparePoint: function(node, offset) { - assertRangeValid(this); - assertNode(node, "HIERARCHY_REQUEST_ERR"); - assertSameDocumentOrFragment(node, this.startContainer); - - if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { - return -1; - } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { - return 1; - } - return 0; - }, - - createContextualFragment: createContextualFragment, - - toHtml: function() { - assertRangeValid(this); - var container = getRangeDocument(this).createElement("div"); - container.appendChild(this.cloneContents()); - return container.innerHTML; - }, - - // touchingIsIntersecting determines whether this method considers a node that borders a range intersects - // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) - intersectsNode: function(node, touchingIsIntersecting) { - assertRangeValid(this); - assertNode(node, "NOT_FOUND_ERR"); - if (dom.getDocument(node) !== getRangeDocument(this)) { - return false; - } - - var parent = node.parentNode, offset = dom.getNodeIndex(node); - assertNode(parent, "NOT_FOUND_ERR"); - - var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset), - endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - }, - - - isPointInRange: function(node, offset) { - assertRangeValid(this); - assertNode(node, "HIERARCHY_REQUEST_ERR"); - assertSameDocumentOrFragment(node, this.startContainer); - - return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && - (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); - }, - - // The methods below are non-standard and invented by me. - - // Sharing a boundary start-to-end or end-to-start does not count as intersection. - intersectsRange: function(range, touchingIsIntersecting) { - assertRangeValid(this); - - if (getRangeDocument(range) != getRangeDocument(this)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - - var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset), - endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - }, - - intersection: function(range) { - if (this.intersectsRange(range)) { - var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), - endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); - - var intersectionRange = this.cloneRange(); - - if (startComparison == -1) { - intersectionRange.setStart(range.startContainer, range.startOffset); - } - if (endComparison == 1) { - intersectionRange.setEnd(range.endContainer, range.endOffset); - } - return intersectionRange; - } - return null; - }, - - union: function(range) { - if (this.intersectsRange(range, true)) { - var unionRange = this.cloneRange(); - if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { - unionRange.setStart(range.startContainer, range.startOffset); - } - if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { - unionRange.setEnd(range.endContainer, range.endOffset); - } - return unionRange; - } else { - throw new RangeException("Ranges do not intersect"); - } - }, - - containsNode: function(node, allowPartial) { - if (allowPartial) { - return this.intersectsNode(node, false); - } else { - return this.compareNode(node) == n_i; - } - }, - - containsNodeContents: function(node) { - return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0; - }, - - containsRange: function(range) { - return this.intersection(range).equals(range); - }, - - containsNodeText: function(node) { - var nodeRange = this.cloneRange(); - nodeRange.selectNode(node); - var textNodes = nodeRange.getNodes([3]); - if (textNodes.length > 0) { - nodeRange.setStart(textNodes[0], 0); - var lastTextNode = textNodes.pop(); - nodeRange.setEnd(lastTextNode, lastTextNode.length); - var contains = this.containsRange(nodeRange); - nodeRange.detach(); - return contains; - } else { - return this.containsNodeContents(node); - } - }, - - createNodeIterator: function(nodeTypes, filter) { - assertRangeValid(this); - return new RangeNodeIterator(this, nodeTypes, filter); - }, - - getNodes: function(nodeTypes, filter) { - assertRangeValid(this); - return getNodesInRange(this, nodeTypes, filter); - }, - - getDocument: function() { - return getRangeDocument(this); - }, - - collapseBefore: function(node) { - assertNotDetached(this); - - this.setEndBefore(node); - this.collapse(false); - }, - - collapseAfter: function(node) { - assertNotDetached(this); - - this.setStartAfter(node); - this.collapse(true); - }, - - getName: function() { - return "DomRange"; - }, - - equals: function(range) { - return Range.rangesEqual(this, range); - }, - - inspect: function() { - return inspect(this); - } - }; - - function copyComparisonConstantsToObject(obj) { - obj.START_TO_START = s2s; - obj.START_TO_END = s2e; - obj.END_TO_END = e2e; - obj.END_TO_START = e2s; - - obj.NODE_BEFORE = n_b; - obj.NODE_AFTER = n_a; - obj.NODE_BEFORE_AND_AFTER = n_b_a; - obj.NODE_INSIDE = n_i; - } - - function copyComparisonConstants(constructor) { - copyComparisonConstantsToObject(constructor); - copyComparisonConstantsToObject(constructor.prototype); - } - - function createRangeContentRemover(remover, boundaryUpdater) { - return function() { - assertRangeValid(this); - - var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; - - var iterator = new RangeIterator(this, true); - - // Work out where to position the range after content removal - var node, boundary; - if (sc !== root) { - node = dom.getClosestAncestorIn(sc, root, true); - boundary = getBoundaryAfterNode(node); - sc = boundary.node; - so = boundary.offset; - } - - // Check none of the range is read-only - iterateSubtree(iterator, assertNodeNotReadOnly); - - iterator.reset(); - - // Remove the content - var returnValue = remover(iterator); - iterator.detach(); - - // Move to the new position - boundaryUpdater(this, sc, so, sc, so); - - return returnValue; - }; - } - - function createPrototypeRange(constructor, boundaryUpdater, detacher) { - function createBeforeAfterNodeSetter(isBefore, isStart) { - return function(node) { - assertNotDetached(this); - assertValidNodeType(node, beforeAfterNodeTypes); - assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); - - var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); - (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); - }; - } - - function setRangeStart(range, node, offset) { - var ec = range.endContainer, eo = range.endOffset; - if (node !== range.startContainer || offset !== range.startOffset) { - // Check the root containers of the range and the new boundary, and also check whether the new boundary - // is after the current end. In either case, collapse the range to the new position - if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) { - ec = node; - eo = offset; - } - boundaryUpdater(range, node, offset, ec, eo); - } - } - - function setRangeEnd(range, node, offset) { - var sc = range.startContainer, so = range.startOffset; - if (node !== range.endContainer || offset !== range.endOffset) { - // Check the root containers of the range and the new boundary, and also check whether the new boundary - // is after the current end. In either case, collapse the range to the new position - if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) { - sc = node; - so = offset; - } - boundaryUpdater(range, sc, so, node, offset); - } - } - - function setRangeStartAndEnd(range, node, offset) { - if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) { - boundaryUpdater(range, node, offset, node, offset); - } - } - - constructor.prototype = new RangePrototype(); - - api.util.extend(constructor.prototype, { - setStart: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeStart(this, node, offset); - }, - - setEnd: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeEnd(this, node, offset); - }, - - setStartBefore: createBeforeAfterNodeSetter(true, true), - setStartAfter: createBeforeAfterNodeSetter(false, true), - setEndBefore: createBeforeAfterNodeSetter(true, false), - setEndAfter: createBeforeAfterNodeSetter(false, false), - - collapse: function(isStart) { - assertRangeValid(this); - if (isStart) { - boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); - } else { - boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); - } - }, - - selectNodeContents: function(node) { - // This doesn't seem well specified: the spec talks only about selecting the node's contents, which - // could be taken to mean only its children. However, browsers implement this the same as selectNode for - // text nodes, so I shall do likewise - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - - boundaryUpdater(this, node, 0, node, dom.getNodeLength(node)); - }, - - selectNode: function(node) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, false); - assertValidNodeType(node, beforeAfterNodeTypes); - - var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); - boundaryUpdater(this, start.node, start.offset, end.node, end.offset); - }, - - extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), - - deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), - - canSurroundContents: function() { - assertRangeValid(this); - assertNodeNotReadOnly(this.startContainer); - assertNodeNotReadOnly(this.endContainer); - - // Check if the contents can be surrounded. Specifically, this means whether the range partially selects - // no non-text nodes. - var iterator = new RangeIterator(this, true); - var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || - (iterator._last && isNonTextPartiallySelected(iterator._last, this))); - iterator.detach(); - return !boundariesInvalid; - }, - - detach: function() { - detacher(this); - }, - - splitBoundaries: function() { - assertRangeValid(this); - - - var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; - var startEndSame = (sc === ec); - - if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { - dom.splitDataNode(ec, eo); - - } - - if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) { - - sc = dom.splitDataNode(sc, so); - if (startEndSame) { - eo -= so; - ec = sc; - } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) { - eo++; - } - so = 0; - - } - boundaryUpdater(this, sc, so, ec, eo); - }, - - normalizeBoundaries: function() { - assertRangeValid(this); - - var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; - - var mergeForward = function(node) { - var sibling = node.nextSibling; - if (sibling && sibling.nodeType == node.nodeType) { - ec = node; - eo = node.length; - node.appendData(sibling.data); - sibling.parentNode.removeChild(sibling); - } - }; - - var mergeBackward = function(node) { - var sibling = node.previousSibling; - if (sibling && sibling.nodeType == node.nodeType) { - sc = node; - var nodeLength = node.length; - so = sibling.length; - node.insertData(0, sibling.data); - sibling.parentNode.removeChild(sibling); - if (sc == ec) { - eo += so; - ec = sc; - } else if (ec == node.parentNode) { - var nodeIndex = dom.getNodeIndex(node); - if (eo == nodeIndex) { - ec = node; - eo = nodeLength; - } else if (eo > nodeIndex) { - eo--; - } - } - } - }; - - var normalizeStart = true; - - if (dom.isCharacterDataNode(ec)) { - if (ec.length == eo) { - mergeForward(ec); - } - } else { - if (eo > 0) { - var endNode = ec.childNodes[eo - 1]; - if (endNode && dom.isCharacterDataNode(endNode)) { - mergeForward(endNode); - } - } - normalizeStart = !this.collapsed; - } - - if (normalizeStart) { - if (dom.isCharacterDataNode(sc)) { - if (so == 0) { - mergeBackward(sc); - } - } else { - if (so < sc.childNodes.length) { - var startNode = sc.childNodes[so]; - if (startNode && dom.isCharacterDataNode(startNode)) { - mergeBackward(startNode); - } - } - } - } else { - sc = ec; - so = eo; - } - - boundaryUpdater(this, sc, so, ec, eo); - }, - - collapseToPoint: function(node, offset) { - assertNotDetached(this); - - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeStartAndEnd(this, node, offset); - } - }); - - copyComparisonConstants(constructor); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Updates commonAncestorContainer and collapsed after boundary change - function updateCollapsedAndCommonAncestor(range) { - range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); - range.commonAncestorContainer = range.collapsed ? - range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); - } - - function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { - var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset); - var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset); - - range.startContainer = startContainer; - range.startOffset = startOffset; - range.endContainer = endContainer; - range.endOffset = endOffset; - - updateCollapsedAndCommonAncestor(range); - dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved}); - } - - function detach(range) { - assertNotDetached(range); - range.startContainer = range.startOffset = range.endContainer = range.endOffset = null; - range.collapsed = range.commonAncestorContainer = null; - dispatchEvent(range, "detach", null); - range._listeners = null; - } - - /** - * @constructor - */ - function Range(doc) { - this.startContainer = doc; - this.startOffset = 0; - this.endContainer = doc; - this.endOffset = 0; - this._listeners = { - boundarychange: [], - detach: [] - }; - updateCollapsedAndCommonAncestor(this); - } - - createPrototypeRange(Range, updateBoundaries, detach); - - api.rangePrototype = RangePrototype.prototype; - - Range.rangeProperties = rangeProperties; - Range.RangeIterator = RangeIterator; - Range.copyComparisonConstants = copyComparisonConstants; - Range.createPrototypeRange = createPrototypeRange; - Range.inspect = inspect; - Range.getRangeDocument = getRangeDocument; - Range.rangesEqual = function(r1, r2) { - return r1.startContainer === r2.startContainer && - r1.startOffset === r2.startOffset && - r1.endContainer === r2.endContainer && - r1.endOffset === r2.endOffset; - }; - - api.DomRange = Range; - api.RangeException = RangeException; -});rangy.createModule("WrappedRange", function(api, module) { - api.requireModules( ["DomUtil", "DomRange"] ); - - /** - * @constructor - */ - var WrappedRange; - var dom = api.dom; - var DomPosition = dom.DomPosition; - var DomRange = api.DomRange; - - - - /*----------------------------------------------------------------------------------------------------------------*/ - - /* - This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() - method. For example, in the following (where pipes denote the selection boundaries): - - <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul> - - var range = document.selection.createRange(); - alert(range.parentElement().id); // Should alert "ul" but alerts "b" - - This method returns the common ancestor node of the following: - - the parentElement() of the textRange - - the parentElement() of the textRange after calling collapse(true) - - the parentElement() of the textRange after calling collapse(false) - */ - function getTextRangeContainerElement(textRange) { - var parentEl = textRange.parentElement(); - - var range = textRange.duplicate(); - range.collapse(true); - var startEl = range.parentElement(); - range = textRange.duplicate(); - range.collapse(false); - var endEl = range.parentElement(); - var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); - - return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); - } - - function textRangeIsCollapsed(textRange) { - return textRange.compareEndPoints("StartToEnd", textRange) == 0; - } - - // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as - // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has - // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling - // for inputs and images, plus optimizations. - function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) { - var workingRange = textRange.duplicate(); - - workingRange.collapse(isStart); - var containerElement = workingRange.parentElement(); - - // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so - // check for that - // TODO: Find out when. Workaround for wholeRangeContainerElement may break this - if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) { - containerElement = wholeRangeContainerElement; - - } - - - - // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and - // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx - if (!containerElement.canHaveHTML) { - return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); - } - - var workingNode = dom.getDocument(containerElement).createElement("span"); - var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; - var previousNode, nextNode, boundaryPosition, boundaryNode; - - // Move the working range through the container's children, starting at the end and working backwards, until the - // working range reaches or goes past the boundary we're interested in - do { - containerElement.insertBefore(workingNode, workingNode.previousSibling); - workingRange.moveToElementText(workingNode); - } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && - workingNode.previousSibling); - - // We've now reached or gone past the boundary of the text range we're interested in - // so have identified the node we want - boundaryNode = workingNode.nextSibling; - - if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) { - // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the - // node containing the text range's boundary, so we move the end of the working range to the boundary point - // and measure the length of its text to get the boundary's offset within the node. - workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); - - - var offset; - - if (/[\r\n]/.test(boundaryNode.data)) { - /* - For the particular case of a boundary within a text node containing line breaks (within a <pre> element, - for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts: - - - Each line break is represented as \r in the text node's data/nodeValue properties - - Each line break is represented as \r\n in the TextRange's 'text' property - - The 'text' property of the TextRange does not contain trailing line breaks - - To get round the problem presented by the final fact above, we can use the fact that TextRange's - moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily - the same as the number of characters it was instructed to move. The simplest approach is to use this to - store the characters moved when moving both the start and end of the range to the start of the document - body and subtracting the start offset from the end offset (the "move-negative-gazillion" method). - However, this is extremely slow when the document is large and the range is near the end of it. Clearly - doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same - problem. - - Another approach that works is to use moveStart() to move the start boundary of the range up to the end - boundary one character at a time and incrementing a counter with the value returned by the moveStart() - call. However, the check for whether the start boundary has reached the end boundary is expensive, so - this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of - the range within the document). - - The method below is a hybrid of the two methods above. It uses the fact that a string containing the - TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the - text of the TextRange, so the start of the range is moved that length initially and then a character at - a time to make up for any trailing line breaks not contained in the 'text' property. This has good - performance in most situations compared to the previous two methods. - */ - var tempRange = workingRange.duplicate(); - var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length; - - offset = tempRange.moveStart("character", rangeLength); - while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) { - offset++; - tempRange.moveStart("character", 1); - } - } else { - offset = workingRange.text.length; - } - boundaryPosition = new DomPosition(boundaryNode, offset); - } else { - - - // If the boundary immediately follows a character data node and this is the end boundary, we should favour - // a position within that, and likewise for a start boundary preceding a character data node - previousNode = (isCollapsed || !isStart) && workingNode.previousSibling; - nextNode = (isCollapsed || isStart) && workingNode.nextSibling; - - - - if (nextNode && dom.isCharacterDataNode(nextNode)) { - boundaryPosition = new DomPosition(nextNode, 0); - } else if (previousNode && dom.isCharacterDataNode(previousNode)) { - boundaryPosition = new DomPosition(previousNode, previousNode.length); - } else { - boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode)); - } - } - - // Clean up - workingNode.parentNode.removeChild(workingNode); - - return boundaryPosition; - } - - // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node. - // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange - // (http://code.google.com/p/ierange/) - function createBoundaryTextRange(boundaryPosition, isStart) { - var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset; - var doc = dom.getDocument(boundaryPosition.node); - var workingNode, childNodes, workingRange = doc.body.createTextRange(); - var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node); - - if (nodeIsDataNode) { - boundaryNode = boundaryPosition.node; - boundaryParent = boundaryNode.parentNode; - } else { - childNodes = boundaryPosition.node.childNodes; - boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null; - boundaryParent = boundaryPosition.node; - } - - // Position the range immediately before the node containing the boundary - workingNode = doc.createElement("span"); - - // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the - // element rather than immediately before or after it, which is what we want - workingNode.innerHTML = "&#feff;"; - - // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report - // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12 - if (boundaryNode) { - boundaryParent.insertBefore(workingNode, boundaryNode); - } else { - boundaryParent.appendChild(workingNode); - } - - workingRange.moveToElementText(workingNode); - workingRange.collapse(!isStart); - - // Clean up - boundaryParent.removeChild(workingNode); - - // Move the working range to the text offset, if required - if (nodeIsDataNode) { - workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset); - } - - return workingRange; - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) { - // This is a wrapper around the browser's native DOM Range. It has two aims: - // - Provide workarounds for specific browser bugs - // - provide convenient extensions, which are inherited from Rangy's DomRange - - (function() { - var rangeProto; - var rangeProperties = DomRange.rangeProperties; - var canSetRangeStartAfterEnd; - - function updateRangeProperties(range) { - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = range.nativeRange[prop]; - } - } - - function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) { - var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); - var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); - - // Always set both boundaries for the benefit of IE9 (see issue 35) - if (startMoved || endMoved) { - range.setEnd(endContainer, endOffset); - range.setStart(startContainer, startOffset); - } - } - - function detach(range) { - range.nativeRange.detach(); - range.detached = true; - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = null; - } - } - - var createBeforeAfterNodeSetter; - - WrappedRange = function(range) { - if (!range) { - throw new Error("Range must be specified"); - } - this.nativeRange = range; - updateRangeProperties(this); - }; - - DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach); - - rangeProto = WrappedRange.prototype; - - rangeProto.selectNode = function(node) { - this.nativeRange.selectNode(node); - updateRangeProperties(this); - }; - - rangeProto.deleteContents = function() { - this.nativeRange.deleteContents(); - updateRangeProperties(this); - }; - - rangeProto.extractContents = function() { - var frag = this.nativeRange.extractContents(); - updateRangeProperties(this); - return frag; - }; - - rangeProto.cloneContents = function() { - return this.nativeRange.cloneContents(); - }; - - // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still - // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for - // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of - // insertNode, which works but is almost certainly slower than the native implementation. -/* - rangeProto.insertNode = function(node) { - this.nativeRange.insertNode(node); - updateRangeProperties(this); - }; -*/ - - rangeProto.surroundContents = function(node) { - this.nativeRange.surroundContents(node); - updateRangeProperties(this); - }; - - rangeProto.collapse = function(isStart) { - this.nativeRange.collapse(isStart); - updateRangeProperties(this); - }; - - rangeProto.cloneRange = function() { - return new WrappedRange(this.nativeRange.cloneRange()); - }; - - rangeProto.refresh = function() { - updateRangeProperties(this); - }; - - rangeProto.toString = function() { - return this.nativeRange.toString(); - }; - - // Create test range and node for feature detection - - var testTextNode = document.createTextNode("test"); - dom.getBody(document).appendChild(testTextNode); - var range = document.createRange(); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and - // correct for it - - range.setStart(testTextNode, 0); - range.setEnd(testTextNode, 0); - - try { - range.setStart(testTextNode, 1); - canSetRangeStartAfterEnd = true; - - rangeProto.setStart = function(node, offset) { - this.nativeRange.setStart(node, offset); - updateRangeProperties(this); - }; - - rangeProto.setEnd = function(node, offset) { - this.nativeRange.setEnd(node, offset); - updateRangeProperties(this); - }; - - createBeforeAfterNodeSetter = function(name) { - return function(node) { - this.nativeRange[name](node); - updateRangeProperties(this); - }; - }; - - } catch(ex) { - - - canSetRangeStartAfterEnd = false; - - rangeProto.setStart = function(node, offset) { - try { - this.nativeRange.setStart(node, offset); - } catch (ex) { - this.nativeRange.setEnd(node, offset); - this.nativeRange.setStart(node, offset); - } - updateRangeProperties(this); - }; - - rangeProto.setEnd = function(node, offset) { - try { - this.nativeRange.setEnd(node, offset); - } catch (ex) { - this.nativeRange.setStart(node, offset); - this.nativeRange.setEnd(node, offset); - } - updateRangeProperties(this); - }; - - createBeforeAfterNodeSetter = function(name, oppositeName) { - return function(node) { - try { - this.nativeRange[name](node); - } catch (ex) { - this.nativeRange[oppositeName](node); - this.nativeRange[name](node); - } - updateRangeProperties(this); - }; - }; - } - - rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); - rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); - rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); - rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to - // the 0th character of the text node - range.selectNodeContents(testTextNode); - if (range.startContainer == testTextNode && range.endContainer == testTextNode && - range.startOffset == 0 && range.endOffset == testTextNode.length) { - rangeProto.selectNodeContents = function(node) { - this.nativeRange.selectNodeContents(node); - updateRangeProperties(this); - }; - } else { - rangeProto.selectNodeContents = function(node) { - this.setStart(node, 0); - this.setEnd(node, DomRange.getEndOffset(node)); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants - // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 - - range.selectNodeContents(testTextNode); - range.setEnd(testTextNode, 3); - - var range2 = document.createRange(); - range2.selectNodeContents(testTextNode); - range2.setEnd(testTextNode, 4); - range2.setStart(testTextNode, 2); - - if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 & - range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { - // This is the wrong way round, so correct for it - - - rangeProto.compareBoundaryPoints = function(type, range) { - range = range.nativeRange || range; - if (type == range.START_TO_END) { - type = range.END_TO_START; - } else if (type == range.END_TO_START) { - type = range.START_TO_END; - } - return this.nativeRange.compareBoundaryPoints(type, range); - }; - } else { - rangeProto.compareBoundaryPoints = function(type, range) { - return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for existence of createContextualFragment and delegate to it if it exists - if (api.util.isHostMethod(range, "createContextualFragment")) { - rangeProto.createContextualFragment = function(fragmentStr) { - return this.nativeRange.createContextualFragment(fragmentStr); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Clean up - dom.getBody(document).removeChild(testTextNode); - range.detach(); - range2.detach(); - })(); - - api.createNativeRange = function(doc) { - doc = doc || document; - return doc.createRange(); - }; - } else if (api.features.implementsTextRange) { - // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a - // prototype - - WrappedRange = function(textRange) { - this.textRange = textRange; - this.refresh(); - }; - - WrappedRange.prototype = new DomRange(document); - - WrappedRange.prototype.refresh = function() { - var start, end; - - // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that. - var rangeContainerElement = getTextRangeContainerElement(this.textRange); - - if (textRangeIsCollapsed(this.textRange)) { - end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true); - } else { - - start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false); - end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false); - } - - this.setStart(start.node, start.offset); - this.setEnd(end.node, end.offset); - }; - - DomRange.copyComparisonConstants(WrappedRange); - - // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work - var globalObj = (function() { return this; })(); - if (typeof globalObj.Range == "undefined") { - globalObj.Range = WrappedRange; - } - - api.createNativeRange = function(doc) { - doc = doc || document; - return doc.body.createTextRange(); - }; - } - - if (api.features.implementsTextRange) { - WrappedRange.rangeToTextRange = function(range) { - if (range.collapsed) { - var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); - - - - return tr; - - //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); - } else { - var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); - var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false); - var textRange = dom.getDocument(range.startContainer).body.createTextRange(); - textRange.setEndPoint("StartToStart", startRange); - textRange.setEndPoint("EndToEnd", endRange); - return textRange; - } - }; - } - - WrappedRange.prototype.getName = function() { - return "WrappedRange"; - }; - - api.WrappedRange = WrappedRange; - - api.createRange = function(doc) { - doc = doc || document; - return new WrappedRange(api.createNativeRange(doc)); - }; - - api.createRangyRange = function(doc) { - doc = doc || document; - return new DomRange(doc); - }; - - api.createIframeRange = function(iframeEl) { - return api.createRange(dom.getIframeDocument(iframeEl)); - }; - - api.createIframeRangyRange = function(iframeEl) { - return api.createRangyRange(dom.getIframeDocument(iframeEl)); - }; - - api.addCreateMissingNativeApiListener(function(win) { - var doc = win.document; - if (typeof doc.createRange == "undefined") { - doc.createRange = function() { - return api.createRange(this); - }; - } - doc = win = null; - }); -});rangy.createModule("WrappedSelection", function(api, module) { - // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range - // spec (http://html5.org/specs/dom-range.html) - - api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] ); - - api.config.checkSelectionRanges = true; - - var BOOLEAN = "boolean", - windowPropertyName = "_rangySelection", - dom = api.dom, - util = api.util, - DomRange = api.DomRange, - WrappedRange = api.WrappedRange, - DOMException = api.DOMException, - DomPosition = dom.DomPosition, - getSelection, - selectionIsCollapsed, - CONTROL = "Control"; - - - - function getWinSelection(winParam) { - return (winParam || window).getSelection(); - } - - function getDocSelection(winParam) { - return (winParam || window).document.selection; - } - - // Test for the Range/TextRange and Selection features required - // Test for ability to retrieve selection - var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"), - implementsDocSelection = api.util.isHostObject(document, "selection"); - - var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange); - - if (useDocumentSelection) { - getSelection = getDocSelection; - api.isSelectionValid = function(winParam) { - var doc = (winParam || window).document, nativeSel = doc.selection; - - // Check whether the selection TextRange is actually contained within the correct document - return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc); - }; - } else if (implementsWinGetSelection) { - getSelection = getWinSelection; - api.isSelectionValid = function() { - return true; - }; - } else { - module.fail("Neither document.selection or window.getSelection() detected."); - } - - api.getNativeSelection = getSelection; - - var testSelection = getSelection(); - var testRange = api.createNativeRange(document); - var body = dom.getBody(document); - - // Obtaining a range from a selection - var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] && - util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"])); - api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus; - - // Test for existence of native selection extend() method - var selectionHasExtend = util.isHostMethod(testSelection, "extend"); - api.features.selectionHasExtend = selectionHasExtend; - - // Test if rangeCount exists - var selectionHasRangeCount = (typeof testSelection.rangeCount == "number"); - api.features.selectionHasRangeCount = selectionHasRangeCount; - - var selectionSupportsMultipleRanges = false; - var collapsedNonEditableSelectionsSupported = true; - - if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) && - typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) { - - (function() { - var iframe = document.createElement("iframe"); - body.appendChild(iframe); - - var iframeDoc = dom.getIframeDocument(iframe); - iframeDoc.open(); - iframeDoc.write("<html><head></head><body>12</body></html>"); - iframeDoc.close(); - - var sel = dom.getIframeWindow(iframe).getSelection(); - var docEl = iframeDoc.documentElement; - var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild; - - // Test whether the native selection will allow a collapsed selection within a non-editable element - var r1 = iframeDoc.createRange(); - r1.setStart(textNode, 1); - r1.collapse(true); - sel.addRange(r1); - collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1); - sel.removeAllRanges(); - - // Test whether the native selection is capable of supporting multiple ranges - var r2 = r1.cloneRange(); - r1.setStart(textNode, 0); - r2.setEnd(textNode, 2); - sel.addRange(r1); - sel.addRange(r2); - - selectionSupportsMultipleRanges = (sel.rangeCount == 2); - - // Clean up - r1.detach(); - r2.detach(); - - body.removeChild(iframe); - })(); - } - - api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges; - api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported; - - // ControlRanges - var implementsControlRange = false, testControlRange; - - if (body && util.isHostMethod(body, "createControlRange")) { - testControlRange = body.createControlRange(); - if (util.areHostProperties(testControlRange, ["item", "add"])) { - implementsControlRange = true; - } - } - api.features.implementsControlRange = implementsControlRange; - - // Selection collapsedness - if (selectionHasAnchorAndFocus) { - selectionIsCollapsed = function(sel) { - return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset; - }; - } else { - selectionIsCollapsed = function(sel) { - return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false; - }; - } - - function updateAnchorAndFocusFromRange(sel, range, backwards) { - var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end"; - sel.anchorNode = range[anchorPrefix + "Container"]; - sel.anchorOffset = range[anchorPrefix + "Offset"]; - sel.focusNode = range[focusPrefix + "Container"]; - sel.focusOffset = range[focusPrefix + "Offset"]; - } - - function updateAnchorAndFocusFromNativeSelection(sel) { - var nativeSel = sel.nativeSelection; - sel.anchorNode = nativeSel.anchorNode; - sel.anchorOffset = nativeSel.anchorOffset; - sel.focusNode = nativeSel.focusNode; - sel.focusOffset = nativeSel.focusOffset; - } - - function updateEmptySelection(sel) { - sel.anchorNode = sel.focusNode = null; - sel.anchorOffset = sel.focusOffset = 0; - sel.rangeCount = 0; - sel.isCollapsed = true; - sel._ranges.length = 0; - } - - function getNativeRange(range) { - var nativeRange; - if (range instanceof DomRange) { - nativeRange = range._selectionNativeRange; - if (!nativeRange) { - nativeRange = api.createNativeRange(dom.getDocument(range.startContainer)); - nativeRange.setEnd(range.endContainer, range.endOffset); - nativeRange.setStart(range.startContainer, range.startOffset); - range._selectionNativeRange = nativeRange; - range.attachListener("detach", function() { - - this._selectionNativeRange = null; - }); - } - } else if (range instanceof WrappedRange) { - nativeRange = range.nativeRange; - } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) { - nativeRange = range; - } - return nativeRange; - } - - function rangeContainsSingleElement(rangeNodes) { - if (!rangeNodes.length || rangeNodes[0].nodeType != 1) { - return false; - } - for (var i = 1, len = rangeNodes.length; i < len; ++i) { - if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) { - return false; - } - } - return true; - } - - function getSingleElementFromRange(range) { - var nodes = range.getNodes(); - if (!rangeContainsSingleElement(nodes)) { - throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element"); - } - return nodes[0]; - } - - function isTextRange(range) { - return !!range && typeof range.text != "undefined"; - } - - function updateFromTextRange(sel, range) { - // Create a Range from the selected TextRange - var wrappedRange = new WrappedRange(range); - sel._ranges = [wrappedRange]; - - updateAnchorAndFocusFromRange(sel, wrappedRange, false); - sel.rangeCount = 1; - sel.isCollapsed = wrappedRange.collapsed; - } - - function updateControlSelection(sel) { - // Update the wrapped selection based on what's now in the native selection - sel._ranges.length = 0; - if (sel.docSelection.type == "None") { - updateEmptySelection(sel); - } else { - var controlRange = sel.docSelection.createRange(); - if (isTextRange(controlRange)) { - // This case (where the selection type is "Control" and calling createRange() on the selection returns - // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected - // ControlRange have been removed from the ControlRange and removed from the document. - updateFromTextRange(sel, controlRange); - } else { - sel.rangeCount = controlRange.length; - var range, doc = dom.getDocument(controlRange.item(0)); - for (var i = 0; i < sel.rangeCount; ++i) { - range = api.createRange(doc); - range.selectNode(controlRange.item(i)); - sel._ranges.push(range); - } - sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed; - updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false); - } - } - } - - function addRangeToControlSelection(sel, range) { - var controlRange = sel.docSelection.createRange(); - var rangeElement = getSingleElementFromRange(range); - - // Create a new ControlRange containing all the elements in the selected ControlRange plus the element - // contained by the supplied range - var doc = dom.getDocument(controlRange.item(0)); - var newControlRange = dom.getBody(doc).createControlRange(); - for (var i = 0, len = controlRange.length; i < len; ++i) { - newControlRange.add(controlRange.item(i)); - } - try { - newControlRange.add(rangeElement); - } catch (ex) { - throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)"); - } - newControlRange.select(); - - // Update the wrapped selection based on what's now in the native selection - updateControlSelection(sel); - } - - var getSelectionRangeAt; - - if (util.isHostMethod(testSelection, "getRangeAt")) { - getSelectionRangeAt = function(sel, index) { - try { - return sel.getRangeAt(index); - } catch(ex) { - return null; - } - }; - } else if (selectionHasAnchorAndFocus) { - getSelectionRangeAt = function(sel) { - var doc = dom.getDocument(sel.anchorNode); - var range = api.createRange(doc); - range.setStart(sel.anchorNode, sel.anchorOffset); - range.setEnd(sel.focusNode, sel.focusOffset); - - // Handle the case when the selection was selected backwards (from the end to the start in the - // document) - if (range.collapsed !== this.isCollapsed) { - range.setStart(sel.focusNode, sel.focusOffset); - range.setEnd(sel.anchorNode, sel.anchorOffset); - } - - return range; - }; - } - - /** - * @constructor - */ - function WrappedSelection(selection, docSelection, win) { - this.nativeSelection = selection; - this.docSelection = docSelection; - this._ranges = []; - this.win = win; - this.refresh(); - } - - api.getSelection = function(win) { - win = win || window; - var sel = win[windowPropertyName]; - var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null; - if (sel) { - sel.nativeSelection = nativeSel; - sel.docSelection = docSel; - sel.refresh(win); - } else { - sel = new WrappedSelection(nativeSel, docSel, win); - win[windowPropertyName] = sel; - } - return sel; - }; - - api.getIframeSelection = function(iframeEl) { - return api.getSelection(dom.getIframeWindow(iframeEl)); - }; - - var selProto = WrappedSelection.prototype; - - function createControlSelection(sel, ranges) { - // Ensure that the selection becomes of type "Control" - var doc = dom.getDocument(ranges[0].startContainer); - var controlRange = dom.getBody(doc).createControlRange(); - for (var i = 0, el; i < rangeCount; ++i) { - el = getSingleElementFromRange(ranges[i]); - try { - controlRange.add(el); - } catch (ex) { - throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)"); - } - } - controlRange.select(); - - // Update the wrapped selection based on what's now in the native selection - updateControlSelection(sel); - } - - // Selecting a range - if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) { - selProto.removeAllRanges = function() { - this.nativeSelection.removeAllRanges(); - updateEmptySelection(this); - }; - - var addRangeBackwards = function(sel, range) { - var doc = DomRange.getRangeDocument(range); - var endRange = api.createRange(doc); - endRange.collapseToPoint(range.endContainer, range.endOffset); - sel.nativeSelection.addRange(getNativeRange(endRange)); - sel.nativeSelection.extend(range.startContainer, range.startOffset); - sel.refresh(); - }; - - if (selectionHasRangeCount) { - selProto.addRange = function(range, backwards) { - if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { - addRangeToControlSelection(this, range); - } else { - if (backwards && selectionHasExtend) { - addRangeBackwards(this, range); - } else { - var previousRangeCount; - if (selectionSupportsMultipleRanges) { - previousRangeCount = this.rangeCount; - } else { - this.removeAllRanges(); - previousRangeCount = 0; - } - this.nativeSelection.addRange(getNativeRange(range)); - - // Check whether adding the range was successful - this.rangeCount = this.nativeSelection.rangeCount; - - if (this.rangeCount == previousRangeCount + 1) { - // The range was added successfully - - // Check whether the range that we added to the selection is reflected in the last range extracted from - // the selection - if (api.config.checkSelectionRanges) { - var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1); - if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) { - // Happens in WebKit with, for example, a selection placed at the start of a text node - range = new WrappedRange(nativeRange); - } - } - this._ranges[this.rangeCount - 1] = range; - updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection)); - this.isCollapsed = selectionIsCollapsed(this); - } else { - // The range was not added successfully. The simplest thing is to refresh - this.refresh(); - } - } - } - }; - } else { - selProto.addRange = function(range, backwards) { - if (backwards && selectionHasExtend) { - addRangeBackwards(this, range); - } else { - this.nativeSelection.addRange(getNativeRange(range)); - this.refresh(); - } - }; - } - - selProto.setRanges = function(ranges) { - if (implementsControlRange && ranges.length > 1) { - createControlSelection(this, ranges); - } else { - this.removeAllRanges(); - for (var i = 0, len = ranges.length; i < len; ++i) { - this.addRange(ranges[i]); - } - } - }; - } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") && - implementsControlRange && useDocumentSelection) { - - selProto.removeAllRanges = function() { - // Added try/catch as fix for issue #21 - try { - this.docSelection.empty(); - - // Check for empty() not working (issue #24) - if (this.docSelection.type != "None") { - // Work around failure to empty a control selection by instead selecting a TextRange and then - // calling empty() - var doc; - if (this.anchorNode) { - doc = dom.getDocument(this.anchorNode); - } else if (this.docSelection.type == CONTROL) { - var controlRange = this.docSelection.createRange(); - if (controlRange.length) { - doc = dom.getDocument(controlRange.item(0)).body.createTextRange(); - } - } - if (doc) { - var textRange = doc.body.createTextRange(); - textRange.select(); - this.docSelection.empty(); - } - } - } catch(ex) {} - updateEmptySelection(this); - }; - - selProto.addRange = function(range) { - if (this.docSelection.type == CONTROL) { - addRangeToControlSelection(this, range); - } else { - WrappedRange.rangeToTextRange(range).select(); - this._ranges[0] = range; - this.rangeCount = 1; - this.isCollapsed = this._ranges[0].collapsed; - updateAnchorAndFocusFromRange(this, range, false); - } - }; - - selProto.setRanges = function(ranges) { - this.removeAllRanges(); - var rangeCount = ranges.length; - if (rangeCount > 1) { - createControlSelection(this, ranges); - } else if (rangeCount) { - this.addRange(ranges[0]); - } - }; - } else { - module.fail("No means of selecting a Range or TextRange was found"); - return false; - } - - selProto.getRangeAt = function(index) { - if (index < 0 || index >= this.rangeCount) { - throw new DOMException("INDEX_SIZE_ERR"); - } else { - return this._ranges[index]; - } - }; - - var refreshSelection; - - if (useDocumentSelection) { - refreshSelection = function(sel) { - var range; - if (api.isSelectionValid(sel.win)) { - range = sel.docSelection.createRange(); - } else { - range = dom.getBody(sel.win.document).createTextRange(); - range.collapse(true); - } - - - if (sel.docSelection.type == CONTROL) { - updateControlSelection(sel); - } else if (isTextRange(range)) { - updateFromTextRange(sel, range); - } else { - updateEmptySelection(sel); - } - }; - } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") { - refreshSelection = function(sel) { - if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) { - updateControlSelection(sel); - } else { - sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount; - if (sel.rangeCount) { - for (var i = 0, len = sel.rangeCount; i < len; ++i) { - sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i)); - } - updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection)); - sel.isCollapsed = selectionIsCollapsed(sel); - } else { - updateEmptySelection(sel); - } - } - }; - } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) { - refreshSelection = function(sel) { - var range, nativeSel = sel.nativeSelection; - if (nativeSel.anchorNode) { - range = getSelectionRangeAt(nativeSel, 0); - sel._ranges = [range]; - sel.rangeCount = 1; - updateAnchorAndFocusFromNativeSelection(sel); - sel.isCollapsed = selectionIsCollapsed(sel); - } else { - updateEmptySelection(sel); - } - }; - } else { - module.fail("No means of obtaining a Range or TextRange from the user's selection was found"); - return false; - } - - selProto.refresh = function(checkForChanges) { - var oldRanges = checkForChanges ? this._ranges.slice(0) : null; - refreshSelection(this); - if (checkForChanges) { - var i = oldRanges.length; - if (i != this._ranges.length) { - return false; - } - while (i--) { - if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) { - return false; - } - } - return true; - } - }; - - // Removal of a single range - var removeRangeManually = function(sel, range) { - var ranges = sel.getAllRanges(), removed = false; - sel.removeAllRanges(); - for (var i = 0, len = ranges.length; i < len; ++i) { - if (removed || range !== ranges[i]) { - sel.addRange(ranges[i]); - } else { - // According to the draft WHATWG Range spec, the same range may be added to the selection multiple - // times. removeRange should only remove the first instance, so the following ensures only the first - // instance is removed - removed = true; - } - } - if (!sel.rangeCount) { - updateEmptySelection(sel); - } - }; - - if (implementsControlRange) { - selProto.removeRange = function(range) { - if (this.docSelection.type == CONTROL) { - var controlRange = this.docSelection.createRange(); - var rangeElement = getSingleElementFromRange(range); - - // Create a new ControlRange containing all the elements in the selected ControlRange minus the - // element contained by the supplied range - var doc = dom.getDocument(controlRange.item(0)); - var newControlRange = dom.getBody(doc).createControlRange(); - var el, removed = false; - for (var i = 0, len = controlRange.length; i < len; ++i) { - el = controlRange.item(i); - if (el !== rangeElement || removed) { - newControlRange.add(controlRange.item(i)); - } else { - removed = true; - } - } - newControlRange.select(); - - // Update the wrapped selection based on what's now in the native selection - updateControlSelection(this); - } else { - removeRangeManually(this, range); - } - }; - } else { - selProto.removeRange = function(range) { - removeRangeManually(this, range); - }; - } - - // Detecting if a selection is backwards - var selectionIsBackwards; - if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) { - selectionIsBackwards = function(sel) { - var backwards = false; - if (sel.anchorNode) { - backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1); - } - return backwards; - }; - - selProto.isBackwards = function() { - return selectionIsBackwards(this); - }; - } else { - selectionIsBackwards = selProto.isBackwards = function() { - return false; - }; - } - - // Selection text - // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation - selProto.toString = function() { - - var rangeTexts = []; - for (var i = 0, len = this.rangeCount; i < len; ++i) { - rangeTexts[i] = "" + this._ranges[i]; - } - return rangeTexts.join(""); - }; - - function assertNodeInSameDocument(sel, node) { - if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - } - - // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used - selProto.collapse = function(node, offset) { - assertNodeInSameDocument(this, node); - var range = api.createRange(dom.getDocument(node)); - range.collapseToPoint(node, offset); - this.removeAllRanges(); - this.addRange(range); - this.isCollapsed = true; - }; - - selProto.collapseToStart = function() { - if (this.rangeCount) { - var range = this._ranges[0]; - this.collapse(range.startContainer, range.startOffset); - } else { - throw new DOMException("INVALID_STATE_ERR"); - } - }; - - selProto.collapseToEnd = function() { - if (this.rangeCount) { - var range = this._ranges[this.rangeCount - 1]; - this.collapse(range.endContainer, range.endOffset); - } else { - throw new DOMException("INVALID_STATE_ERR"); - } - }; - - // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is - // never used by Rangy. - selProto.selectAllChildren = function(node) { - assertNodeInSameDocument(this, node); - var range = api.createRange(dom.getDocument(node)); - range.selectNodeContents(node); - this.removeAllRanges(); - this.addRange(range); - }; - - selProto.deleteFromDocument = function() { - // Sepcial behaviour required for Control selections - if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { - var controlRange = this.docSelection.createRange(); - var element; - while (controlRange.length) { - element = controlRange.item(0); - controlRange.remove(element); - element.parentNode.removeChild(element); - } - this.refresh(); - } else if (this.rangeCount) { - var ranges = this.getAllRanges(); - this.removeAllRanges(); - for (var i = 0, len = ranges.length; i < len; ++i) { - ranges[i].deleteContents(); - } - // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each - // range. Firefox moves the selection to where the final selected range was, so we emulate that - this.addRange(ranges[len - 1]); - } - }; - - // The following are non-standard extensions - selProto.getAllRanges = function() { - return this._ranges.slice(0); - }; - - selProto.setSingleRange = function(range) { - this.setRanges( [range] ); - }; - - selProto.containsNode = function(node, allowPartial) { - for (var i = 0, len = this._ranges.length; i < len; ++i) { - if (this._ranges[i].containsNode(node, allowPartial)) { - return true; - } - } - return false; - }; - - selProto.toHtml = function() { - var html = ""; - if (this.rangeCount) { - var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div"); - for (var i = 0, len = this._ranges.length; i < len; ++i) { - container.appendChild(this._ranges[i].cloneContents()); - } - html = container.innerHTML; - } - return html; - }; - - function inspect(sel) { - var rangeInspects = []; - var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset); - var focus = new DomPosition(sel.focusNode, sel.focusOffset); - var name = (typeof sel.getName == "function") ? sel.getName() : "Selection"; - - if (typeof sel.rangeCount != "undefined") { - for (var i = 0, len = sel.rangeCount; i < len; ++i) { - rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i)); - } - } - return "[" + name + "(Ranges: " + rangeInspects.join(", ") + - ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]"; - - } - - selProto.getName = function() { - return "WrappedSelection"; - }; - - selProto.inspect = function() { - return inspect(this); - }; - - selProto.detach = function() { - this.win[windowPropertyName] = null; - this.win = this.anchorNode = this.focusNode = null; - }; - - WrappedSelection.inspect = inspect; - - api.Selection = WrappedSelection; - - api.selectionPrototype = selProto; - - api.addCreateMissingNativeApiListener(function(win) { - if (typeof win.getSelection == "undefined") { - win.getSelection = function() { - return api.getSelection(this); - }; - } - win = null; - }); -}); -/* - Base.js, version 1.1a - Copyright 2006-2010, Dean Edwards - License: http://www.opensource.org/licenses/mit-license.php -*/ - -var Base = function() { - // dummy -}; - -Base.extend = function(_instance, _static) { // subclass - var extend = Base.prototype.extend; - - // build the prototype - Base._prototyping = true; - var proto = new this; - extend.call(proto, _instance); - proto.base = function() { - // call this method from any other method to invoke that method's ancestor - }; - delete Base._prototyping; - - // create the wrapper for the constructor function - //var constructor = proto.constructor.valueOf(); //-dean - var constructor = proto.constructor; - var klass = proto.constructor = function() { - if (!Base._prototyping) { - if (this._constructing || this.constructor == klass) { // instantiation - this._constructing = true; - constructor.apply(this, arguments); - delete this._constructing; - } else if (arguments[0] != null) { // casting - return (arguments[0].extend || extend).call(arguments[0], proto); - } - } - }; - - // build the class interface - klass.ancestor = this; - klass.extend = this.extend; - klass.forEach = this.forEach; - klass.implement = this.implement; - klass.prototype = proto; - klass.toString = this.toString; - klass.valueOf = function(type) { - //return (type == "object") ? klass : constructor; //-dean - return (type == "object") ? klass : constructor.valueOf(); - }; - extend.call(klass, _static); - // class initialisation - if (typeof klass.init == "function") klass.init(); - return klass; -}; - -Base.prototype = { - extend: function(source, value) { - if (arguments.length > 1) { // extending with a name/value pair - var ancestor = this[source]; - if (ancestor && (typeof value == "function") && // overriding a method? - // the valueOf() comparison is to avoid circular references - (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) && - /\bbase\b/.test(value)) { - // get the underlying method - var method = value.valueOf(); - // override - value = function() { - var previous = this.base || Base.prototype.base; - this.base = ancestor; - var returnValue = method.apply(this, arguments); - this.base = previous; - return returnValue; - }; - // point to the underlying method - value.valueOf = function(type) { - return (type == "object") ? value : method; - }; - value.toString = Base.toString; - } - this[source] = value; - } else if (source) { // extending with an object literal - var extend = Base.prototype.extend; - // if this object has a customised extend method then use it - if (!Base._prototyping && typeof this != "function") { - extend = this.extend || extend; - } - var proto = {toSource: null}; - // do the "toString" and other methods manually - var hidden = ["constructor", "toString", "valueOf"]; - // if we are prototyping then include the constructor - var i = Base._prototyping ? 0 : 1; - while (key = hidden[i++]) { - if (source[key] != proto[key]) { - extend.call(this, key, source[key]); - - } - } - // copy each of the source object's properties to this object - for (var key in source) { - if (!proto[key]) extend.call(this, key, source[key]); - } - } - return this; - } -}; - -// initialise -Base = Base.extend({ - constructor: function() { - this.extend(arguments[0]); - } -}, { - ancestor: Object, - version: "1.1", - - forEach: function(object, block, context) { - for (var key in object) { - if (this.prototype[key] === undefined) { - block.call(context, object[key], key, object); - } - } - }, - - implement: function() { - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] == "function") { - // if it's a function, call it - arguments[i](this.prototype); - } else { - // add the interface using the extend method - this.prototype.extend(arguments[i]); - } - } - return this; - }, - - toString: function() { - return String(this.valueOf()); - } -});/** - * Detect browser support for specific features - */ -wysihtml5.browser = (function() { - var userAgent = navigator.userAgent, - testElement = document.createElement("div"), - // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect - isIE = userAgent.indexOf("MSIE") !== -1 && userAgent.indexOf("Opera") === -1, - isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1, - isWebKit = userAgent.indexOf("AppleWebKit/") !== -1, - isChrome = userAgent.indexOf("Chrome/") !== -1, - isOpera = userAgent.indexOf("Opera/") !== -1; - - function iosVersion(userAgent) { - return ((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [, 0])[1]; - } - - return { - // Static variable needed, publicly accessible, to be able override it in unit tests - USER_AGENT: userAgent, - - /** - * Exclude browsers that are not capable of displaying and handling - * contentEditable as desired: - * - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable - * - IE < 8 create invalid markup and crash randomly from time to time - * - * @return {Boolean} - */ - supported: function() { - var userAgent = this.USER_AGENT.toLowerCase(), - // Essential for making html elements editable - hasContentEditableSupport = "contentEditable" in testElement, - // Following methods are needed in order to interact with the contentEditable area - hasEditingApiSupport = document.execCommand && document.queryCommandSupported && document.queryCommandState, - // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+ - hasQuerySelectorSupport = document.querySelector && document.querySelectorAll, - // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05) - isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1; - - return hasContentEditableSupport - && hasEditingApiSupport - && hasQuerySelectorSupport - && !isIncompatibleMobileBrowser; - }, - - isTouchDevice: function() { - return this.supportsEvent("touchmove"); - }, - - isIos: function() { - var userAgent = this.USER_AGENT.toLowerCase(); - return userAgent.indexOf("webkit") !== -1 && userAgent.indexOf("mobile") !== -1; - }, - - /** - * Whether the browser supports sandboxed iframes - * Currently only IE 6+ offers such feature <iframe security="restricted"> - * - * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx - * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx - * - * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage) - */ - supportsSandboxedIframes: function() { - return isIE; - }, - - /** - * IE6+7 throw a mixed content warning when the src of an iframe - * is empty/unset or about:blank - * window.querySelector is implemented as of IE8 - */ - throwsMixedContentWarningWhenIframeSrcIsEmpty: function() { - return !("querySelector" in document); - }, - - /** - * Whether the caret is correctly displayed in contentEditable elements - * Firefox sometimes shows a huge caret in the beginning after focusing - */ - displaysCaretInEmptyContentEditableCorrectly: function() { - return !isGecko; - }, - - /** - * Opera and IE are the only browsers who offer the css value - * in the original unit, thx to the currentStyle object - * All other browsers provide the computed style in px via window.getComputedStyle - */ - hasCurrentStyleProperty: function() { - return "currentStyle" in testElement; - }, - - /** - * Whether the browser inserts a <br> when pressing enter in a contentEditable element - */ - insertsLineBreaksOnReturn: function() { - return isGecko; - }, - - supportsPlaceholderAttributeOn: function(element) { - return "placeholder" in element; - }, - - supportsEvent: function(eventName) { - return "on" + eventName in testElement || (function() { - testElement.setAttribute("on" + eventName, "return;"); - return typeof(testElement["on" + eventName]) === "function"; - })(); - }, - - /** - * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe - */ - supportsEventsInIframeCorrectly: function() { - return !isOpera; - }, - - /** - * Chrome & Safari only fire the ondrop/ondragend/... events when the ondragover event is cancelled - * with event.preventDefault - * Firefox 3.6 fires those events anyway, but the mozilla doc says that the dragover/dragenter event needs - * to be cancelled - */ - firesOnDropOnlyWhenOnDragOverIsCancelled: function() { - return isWebKit || isGecko; - }, - - /** - * Whether the browser supports the event.dataTransfer property in a proper way - */ - supportsDataTransfer: function() { - try { - // Firefox doesn't support dataTransfer in a safe way, it doesn't strip script code in the html payload (like Chrome does) - return isWebKit && (window.Clipboard || window.DataTransfer).prototype.getData; - } catch(e) { - return false; - } - }, - - /** - * Everything below IE9 doesn't know how to treat HTML5 tags - * - * @param {Object} context The document object on which to check HTML5 support - * - * @example - * wysihtml5.browser.supportsHTML5Tags(document); - */ - supportsHTML5Tags: function(context) { - var element = context.createElement("div"), - html5 = "<article>foo</article>"; - element.innerHTML = html5; - return element.innerHTML.toLowerCase() === html5; - }, - - /** - * Checks whether a document supports a certain queryCommand - * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree - * in oder to report correct results - * - * @param {Object} doc Document object on which to check for a query command - * @param {String} command The query command to check for - * @return {Boolean} - * - * @example - * wysihtml5.browser.supportsCommand(document, "bold"); - */ - supportsCommand: (function() { - // Following commands are supported but contain bugs in some browsers - var buggyCommands = { - // formatBlock fails with some tags (eg. <blockquote>) - "formatBlock": isIE, - // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets - // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>) - // IE and Opera act a bit different here as they convert the entire content of the current block element into a list - "insertUnorderedList": isIE || isOpera || isWebKit, - "insertOrderedList": isIE || isOpera || isWebKit - }; - - // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands - var supported = { - "insertHTML": isGecko - }; - - return function(doc, command) { - var isBuggy = buggyCommands[command]; - if (!isBuggy) { - // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled - try { - return doc.queryCommandSupported(command); - } catch(e1) {} - - try { - return doc.queryCommandEnabled(command); - } catch(e2) { - return !!supported[command]; - } - } - return false; - }; - })(), - - /** - * IE: URLs starting with: - * www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://, - * nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url: - * will automatically be auto-linked when either the user inserts them via copy&paste or presses the - * space bar when the caret is directly after such an url. - * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll - * (related blog post on msdn - * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx). - */ - doesAutoLinkingInContentEditable: function() { - return isIE; - }, - - /** - * As stated above, IE auto links urls typed into contentEditable elements - * Since IE9 it's possible to prevent this behavior - */ - canDisableAutoLinking: function() { - return this.supportsCommand(document, "AutoUrlDetect"); - }, - - /** - * IE leaves an empty paragraph in the contentEditable element after clearing it - * Chrome/Safari sometimes an empty <div> - */ - clearsContentEditableCorrectly: function() { - return isGecko || isOpera || isWebKit; - }, - - /** - * IE gives wrong results for getAttribute - */ - supportsGetAttributeCorrectly: function() { - var td = document.createElement("td"); - return td.getAttribute("rowspan") != "1"; - }, - - /** - * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them. - * Chrome and Safari both don't support this - */ - canSelectImagesInContentEditable: function() { - return isGecko || isIE || isOpera; - }, - - /** - * When the caret is in an empty list (<ul><li>|</li></ul>) which is the first child in an contentEditable container - * pressing backspace doesn't remove the entire list as done in other browsers - */ - clearsListsInContentEditableCorrectly: function() { - return isGecko || isIE || isWebKit; - }, - - /** - * All browsers except Safari and Chrome automatically scroll the range/caret position into view - */ - autoScrollsToCaret: function() { - return !isWebKit; - }, - - /** - * Check whether the browser automatically closes tags that don't need to be opened - */ - autoClosesUnclosedTags: function() { - var clonedTestElement = testElement.cloneNode(false), - returnValue, - innerHTML; - - clonedTestElement.innerHTML = "<p><div></div>"; - innerHTML = clonedTestElement.innerHTML.toLowerCase(); - returnValue = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>"; - - // Cache result by overwriting current function - this.autoClosesUnclosedTags = function() { return returnValue; }; - - return returnValue; - }, - - /** - * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists - */ - supportsNativeGetElementsByClassName: function() { - return String(document.getElementsByClassName).indexOf("[native code]") !== -1; - }, - - /** - * As of now (19.04.2011) only supported by Firefox 4 and Chrome - * See https://developer.mozilla.org/en/DOM/Selection/modify - */ - supportsSelectionModify: function() { - return "getSelection" in window && "modify" in window.getSelection(); - }, - - /** - * Whether the browser supports the classList object for fast className manipulation - * See https://developer.mozilla.org/en/DOM/element.classList - */ - supportsClassList: function() { - return "classList" in testElement; - }, - - /** - * Opera needs a white space after a <br> in order to position the caret correctly - */ - needsSpaceAfterLineBreak: function() { - return isOpera; - }, - - /** - * Whether the browser supports the speech api on the given element - * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/ - * - * @example - * var input = document.createElement("input"); - * if (wysihtml5.browser.supportsSpeechApiOn(input)) { - * // ... - * } - */ - supportsSpeechApiOn: function(input) { - var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [, 0]; - return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input); - }, - - /** - * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest - * See https://connect.microsoft.com/ie/feedback/details/650112 - * or try the POC http://tifftiff.de/ie9_crash/ - */ - crashesWhenDefineProperty: function(property) { - return isIE && (property === "XMLHttpRequest" || property === "XDomainRequest"); - }, - - /** - * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element - */ - doesAsyncFocus: function() { - return isIE; - }, - - /** - * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document - */ - hasProblemsSettingCaretAfterImg: function() { - return isIE; - }, - - hasUndoInContextMenu: function() { - return isGecko || isChrome || isOpera; - } - }; -})();wysihtml5.lang.array = function(arr) { - return { - /** - * Check whether a given object exists in an array - * - * @example - * wysihtml5.lang.array([1, 2]).contains(1); - * // => true - */ - contains: function(needle) { - if (arr.indexOf) { - return arr.indexOf(needle) !== -1; - } else { - for (var i=0, length=arr.length; i<length; i++) { - if (arr[i] === needle) { return true; } - } - return false; - } - }, - - /** - * Substract one array from another - * - * @example - * wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]); - * // => [1, 2] - */ - without: function(arrayToSubstract) { - arrayToSubstract = wysihtml5.lang.array(arrayToSubstract); - var newArr = [], - i = 0, - length = arr.length; - for (; i<length; i++) { - if (!arrayToSubstract.contains(arr[i])) { - newArr.push(arr[i]); - } - } - return newArr; - }, - - /** - * Return a clean native array - * - * Following will convert a Live NodeList to a proper Array - * @example - * var childNodes = wysihtml5.lang.array(document.body.childNodes).get(); - */ - get: function() { - var i = 0, - length = arr.length, - newArray = []; - for (; i<length; i++) { - newArray.push(arr[i]); - } - return newArray; - } - }; -};wysihtml5.lang.Dispatcher = Base.extend( - /** @scope wysihtml5.lang.Dialog.prototype */ { - observe: function(eventName, handler) { - this.events = this.events || {}; - this.events[eventName] = this.events[eventName] || []; - this.events[eventName].push(handler); - return this; - }, - - on: function() { - return this.observe.apply(this, wysihtml5.lang.array(arguments).get()); - }, - - fire: function(eventName, payload) { - this.events = this.events || {}; - var handlers = this.events[eventName] || [], - i = 0; - for (; i<handlers.length; i++) { - handlers[i].call(this, payload); - } - return this; - }, - - stopObserving: function(eventName, handler) { - this.events = this.events || {}; - var i = 0, - handlers, - newHandlers; - if (eventName) { - handlers = this.events[eventName] || [], - newHandlers = []; - for (; i<handlers.length; i++) { - if (handlers[i] !== handler && handler) { - newHandlers.push(handlers[i]); - } - } - this.events[eventName] = newHandlers; - } else { - // Clean up all events - this.events = {}; - } - return this; - } -});wysihtml5.lang.object = function(obj) { - return { - /** - * @example - * wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get(); - * // => { foo: 1, bar: 2, baz: 3 } - */ - merge: function(otherObj) { - for (var i in otherObj) { - obj[i] = otherObj[i]; - } - return this; - }, - - get: function() { - return obj; - }, - - /** - * @example - * wysihtml5.lang.object({ foo: 1 }).clone(); - * // => { foo: 1 } - */ - clone: function() { - var newObj = {}, - i; - for (i in obj) { - newObj[i] = obj[i]; - } - return newObj; - }, - - /** - * @example - * wysihtml5.lang.object([]).isArray(); - * // => true - */ - isArray: function() { - return Object.prototype.toString.call(obj) === "[object Array]"; - } - }; -};(function() { - var WHITE_SPACE_START = /^\s+/, - WHITE_SPACE_END = /\s+$/; - wysihtml5.lang.string = function(str) { - str = String(str); - return { - /** - * @example - * wysihtml5.lang.string(" foo ").trim(); - * // => "foo" - */ - trim: function() { - return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, ""); - }, - - /** - * @example - * wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" }); - * // => "Hello Christopher" - */ - interpolate: function(vars) { - for (var i in vars) { - str = this.replace("#{" + i + "}").by(vars[i]); - } - return str; - }, - - /** - * @example - * wysihtml5.lang.string("Hello Tom").replace("Tom").with("Hans"); - * // => "Hello Hans" - */ - replace: function(search) { - return { - by: function(replace) { - return str.split(search).join(replace); - } - } - } - }; - }; -})();/** - * Find urls in descendant text nodes of an element and auto-links them - * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/ - * - * @param {Element} element Container element in which to search for urls - * - * @example - * <div id="text-container">Please click here: www.google.com</div> - * <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script> - */ -(function(wysihtml5) { - var /** - * Don't auto-link urls that are contained in the following elements: - */ - IGNORE_URLS_IN = wysihtml5.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]), - /** - * revision 1: - * /(\S+\.{1}[^\s\,\.\!]+)/g - * - * revision 2: - * /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim - * - * put this in the beginning if you don't wan't to match within a word - * (^|[\>\(\{\[\s\>]) - */ - URL_REG_EXP = /((https?:\/\/|www\.)[^\s<]{3,})/gi, - TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i, - MAX_DISPLAY_LENGTH = 100, - BRACKETS = { ")": "(", "]": "[", "}": "{" }; - - function autoLink(element) { - if (_hasParentThatShouldBeIgnored(element)) { - return element; - } - - if (element === element.ownerDocument.documentElement) { - element = element.ownerDocument.body; - } - - return _parseNode(element); - } - - /** - * This is basically a rebuild of - * the rails auto_link_urls text helper - */ - function _convertUrlsToLinks(str) { - return str.replace(URL_REG_EXP, function(match, url) { - var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "", - opening = BRACKETS[punctuation]; - url = url.replace(TRAILING_CHAR_REG_EXP, ""); - - if (url.split(opening).length > url.split(punctuation).length) { - url = url + punctuation; - punctuation = ""; - } - var realUrl = url, - displayUrl = url; - if (url.length > MAX_DISPLAY_LENGTH) { - displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "..."; - } - // Add http prefix if necessary - if (realUrl.substr(0, 4) === "www.") { - realUrl = "http://" + realUrl; - } - - return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation; - }); - } - - /** - * Creates or (if already cached) returns a temp element - * for the given document object - */ - function _getTempElement(context) { - var tempElement = context._wysihtml5_tempElement; - if (!tempElement) { - tempElement = context._wysihtml5_tempElement = context.createElement("div"); - } - return tempElement; - } - - /** - * Replaces the original text nodes with the newly auto-linked dom tree - */ - function _wrapMatchesInNode(textNode) { - var parentNode = textNode.parentNode, - tempElement = _getTempElement(parentNode.ownerDocument); - - // We need to insert an empty/temporary <span /> to fix IE quirks - // Elsewise IE would strip white space in the beginning - tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(textNode.data); - tempElement.removeChild(tempElement.firstChild); - - while (tempElement.firstChild) { - // inserts tempElement.firstChild before textNode - parentNode.insertBefore(tempElement.firstChild, textNode); - } - parentNode.removeChild(textNode); - } - - function _hasParentThatShouldBeIgnored(node) { - var nodeName; - while (node.parentNode) { - node = node.parentNode; - nodeName = node.nodeName; - if (IGNORE_URLS_IN.contains(nodeName)) { - return true; - } else if (nodeName === "body") { - return false; - } - } - return false; - } - - function _parseNode(element) { - if (IGNORE_URLS_IN.contains(element.nodeName)) { - return; - } - - if (element.nodeType === wysihtml5.TEXT_NODE && element.data.match(URL_REG_EXP)) { - _wrapMatchesInNode(element); - return; - } - - var childNodes = wysihtml5.lang.array(element.childNodes).get(), - childNodesLength = childNodes.length, - i = 0; - - for (; i<childNodesLength; i++) { - _parseNode(childNodes[i]); - } - - return element; - } - - wysihtml5.dom.autoLink = autoLink; - - // Reveal url reg exp to the outside - wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP; -})(wysihtml5);(function(wysihtml5) { - var supportsClassList = wysihtml5.browser.supportsClassList(), - api = wysihtml5.dom; - - api.addClass = function(element, className) { - if (supportsClassList) { - return element.classList.add(className); - } - if (api.hasClass(element, className)) { - return; - } - element.className += " " + className; - }; - - api.removeClass = function(element, className) { - if (supportsClassList) { - return element.classList.remove(className); - } - - element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " "); - }; - - api.hasClass = function(element, className) { - if (supportsClassList) { - return element.classList.contains(className); - } - - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }; -})(wysihtml5); -wysihtml5.dom.contains = (function() { - var documentElement = document.documentElement; - if (documentElement.contains) { - return function(container, element) { - if (element.nodeType !== wysihtml5.ELEMENT_NODE) { - element = element.parentNode; - } - return container !== element && container.contains(element); - }; - } else if (documentElement.compareDocumentPosition) { - return function(container, element) { - // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition - return !!(container.compareDocumentPosition(element) & 16); - }; - } -})();/** - * Converts an HTML fragment/element into a unordered/ordered list - * - * @param {Element} element The element which should be turned into a list - * @param {String} listType The list type in which to convert the tree (either "ul" or "ol") - * @return {Element} The created list - * - * @example - * <!-- Assume the following dom: --> - * <span id="pseudo-list"> - * eminem<br> - * dr. dre - * <div>50 Cent</div> - * </span> - * - * <script> - * wysihtml5.dom.convertToList(document.getElementById("pseudo-list"), "ul"); - * </script> - * - * <!-- Will result in: --> - * <ul> - * <li>eminem</li> - * <li>dr. dre</li> - * <li>50 Cent</li> - * </ul> - */ -wysihtml5.dom.convertToList = (function() { - function _createListItem(doc, list) { - var listItem = doc.createElement("li"); - list.appendChild(listItem); - return listItem; - } - - function _createList(doc, type) { - return doc.createElement(type); - } - - function convertToList(element, listType) { - if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") { - // Already a list - return element; - } - - var doc = element.ownerDocument, - list = _createList(doc, listType), - lineBreaks = element.querySelectorAll("br"), - lineBreaksLength = lineBreaks.length, - childNodes, - childNodesLength, - childNode, - lineBreak, - parentNode, - isBlockElement, - isLineBreak, - currentListItem, - i; - - // First find <br> at the end of inline elements and move them behind them - for (i=0; i<lineBreaksLength; i++) { - lineBreak = lineBreaks[i]; - while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) { - if (wysihtml5.dom.getStyle("display").from(parentNode) === "block") { - parentNode.removeChild(lineBreak); - break; - } - wysihtml5.dom.insert(lineBreak).after(lineBreak.parentNode); - } - } - - childNodes = wysihtml5.lang.array(element.childNodes).get(); - childNodesLength = childNodes.length; - - for (i=0; i<childNodesLength; i++) { - currentListItem = currentListItem || _createListItem(doc, list); - childNode = childNodes[i]; - isBlockElement = wysihtml5.dom.getStyle("display").from(childNode) === "block"; - isLineBreak = childNode.nodeName === "BR"; - - if (isBlockElement) { - // Append blockElement to current <li> if empty, otherwise create a new one - currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem; - currentListItem.appendChild(childNode); - currentListItem = null; - continue; - } - - if (isLineBreak) { - // Only create a new list item in the next iteration when the current one has already content - currentListItem = currentListItem.firstChild ? null : currentListItem; - continue; - } - - currentListItem.appendChild(childNode); - } - - element.parentNode.replaceChild(list, element); - return list; - } - - return convertToList; -})();/** - * Copy a set of attributes from one element to another - * - * @param {Array} attributesToCopy List of attributes which should be copied - * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to - * copy the attributes from., this again returns an object which provides a method named "to" which can be invoked - * with the element where to copy the attributes to (see example) - * - * @example - * var textarea = document.querySelector("textarea"), - * div = document.querySelector("div[contenteditable=true]"), - * anotherDiv = document.querySelector("div.preview"); - * wysihtml5.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv); - * - */ -wysihtml5.dom.copyAttributes = function(attributesToCopy) { - return { - from: function(elementToCopyFrom) { - return { - to: function(elementToCopyTo) { - var attribute, - i = 0, - length = attributesToCopy.length; - for (; i<length; i++) { - attribute = attributesToCopy[i]; - if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") { - elementToCopyTo[attribute] = elementToCopyFrom[attribute]; - } - } - return { andTo: arguments.callee }; - } - }; - } - }; -};/** - * Copy a set of styles from one element to another - * Please note that this only works properly across browsers when the element from which to copy the styles - * is in the dom - * - * Interesting article on how to copy styles - * - * @param {Array} stylesToCopy List of styles which should be copied - * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to - * copy the styles from., this again returns an object which provides a method named "to" which can be invoked - * with the element where to copy the styles to (see example) - * - * @example - * var textarea = document.querySelector("textarea"), - * div = document.querySelector("div[contenteditable=true]"), - * anotherDiv = document.querySelector("div.preview"); - * wysihtml5.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv); - * - */ -(function(dom) { - - /** - * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set - * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then - * its computed css width will be 198px - */ - var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"]; - - var shouldIgnoreBoxSizingBorderBox = function(element) { - if (hasBoxSizingBorderBox(element)) { - return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth; - } - return false; - }; - - var hasBoxSizingBorderBox = function(element) { - var i = 0, - length = BOX_SIZING_PROPERTIES.length; - for (; i<length; i++) { - if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") { - return BOX_SIZING_PROPERTIES[i]; - } - } - }; - - dom.copyStyles = function(stylesToCopy) { - return { - from: function(element) { - if (shouldIgnoreBoxSizingBorderBox(element)) { - stylesToCopy = wysihtml5.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES); - } - - var cssText = "", - length = stylesToCopy.length, - i = 0, - property; - for (; i<length; i++) { - property = stylesToCopy[i]; - cssText += property + ":" + dom.getStyle(property).from(element) + ";"; - } - - return { - to: function(element) { - dom.setStyles(cssText).on(element); - return { andTo: arguments.callee }; - } - }; - } - }; - }; -})(wysihtml5.dom);/** - * Event Delegation - * - * @example - * wysihtml5.dom.delegate(document.body, "a", "click", function() { - * // foo - * }); - */ -(function(wysihtml5) { - - wysihtml5.dom.delegate = function(container, selector, eventName, handler) { - return wysihtml5.dom.observe(container, eventName, function(event) { - var target = event.target, - match = wysihtml5.lang.array(container.querySelectorAll(selector)); - - while (target && target !== container) { - if (match.contains(target)) { - handler.call(target, event); - break; - } - target = target.parentNode; - } - }); - }; - -})(wysihtml5);/** - * Returns the given html wrapped in a div element - * - * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly - * when inserted via innerHTML - * - * @param {String} html The html which should be wrapped in a dom element - * @param {Obejct} [context] Document object of the context the html belongs to - * - * @example - * wysihtml5.dom.getAsDom("<article>foo</article>"); - */ -wysihtml5.dom.getAsDom = (function() { - - var _innerHTMLShiv = function(html, context) { - var tempElement = context.createElement("div"); - tempElement.style.display = "none"; - context.body.appendChild(tempElement); - // IE throws an exception when trying to insert <frameset></frameset> via innerHTML - try { tempElement.innerHTML = html; } catch(e) {} - context.body.removeChild(tempElement); - return tempElement; - }; - - /** - * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element - */ - var _ensureHTML5Compatibility = function(context) { - if (context._wysihtml5_supportsHTML5Tags) { - return; - } - for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) { - context.createElement(HTML5_ELEMENTS[i]); - } - context._wysihtml5_supportsHTML5Tags = true; - }; - - - /** - * List of html5 tags - * taken from http://simon.html5.org/html5-elements - */ - var HTML5_ELEMENTS = [ - "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption", - "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress", - "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr" - ]; - - return function(html, context) { - context = context || document; - var tempElement; - if (typeof(html) === "object" && html.nodeType) { - tempElement = context.createElement("div"); - tempElement.appendChild(html); - } else if (wysihtml5.browser.supportsHTML5Tags(context)) { - tempElement = context.createElement("div"); - tempElement.innerHTML = html; - } else { - _ensureHTML5Compatibility(context); - tempElement = _innerHTMLShiv(html, context); - } - return tempElement; - }; -})();/** - * Walks the dom tree from the given node up until it finds a match - * Designed for optimal performance. - * - * @param {Element} node The from which to check the parent nodes - * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp) - * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50) - * @return {null|Element} Returns the first element that matched the desiredNodeName(s) - * @example - * var listElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: ["MENU", "UL", "OL"] }); - * // ... or ... - * var unorderedListElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: "UL" }); - * // ... or ... - * var coloredElement = wysihtml5.dom.getParentElement(myTextNode, { nodeName: "SPAN", className: "wysiwyg-color-red", classRegExp: /wysiwyg-color-[a-z]/g }); - */ -wysihtml5.dom.getParentElement = (function() { - - function _isSameNodeName(nodeName, desiredNodeNames) { - if (!desiredNodeNames || !desiredNodeNames.length) { - return true; - } - - if (typeof(desiredNodeNames) === "string") { - return nodeName === desiredNodeNames; - } else { - return wysihtml5.lang.array(desiredNodeNames).contains(nodeName); - } - } - - function _isElement(node) { - return node.nodeType === wysihtml5.ELEMENT_NODE; - } - - function _hasClassName(element, className, classRegExp) { - var classNames = (element.className || "").match(classRegExp) || []; - if (!className) { - return !!classNames.length; - } - return classNames[classNames.length - 1] === className; - } - - function _getParentElementWithNodeName(node, nodeName, levels) { - while (levels-- && node && node.nodeName !== "BODY") { - if (_isSameNodeName(node.nodeName, nodeName)) { - return node; - } - node = node.parentNode; - } - return null; - } - - function _getParentElementWithNodeNameAndClassName(node, nodeName, className, classRegExp, levels) { - while (levels-- && node && node.nodeName !== "BODY") { - if (_isElement(node) && - _isSameNodeName(node.nodeName, nodeName) && - _hasClassName(node, className, classRegExp)) { - return node; - } - node = node.parentNode; - } - return null; - } - - return function(node, matchingSet, levels) { - levels = levels || 50; // Go max 50 nodes upwards from current node - if (matchingSet.className || matchingSet.classRegExp) { - return _getParentElementWithNodeNameAndClassName( - node, matchingSet.nodeName, matchingSet.className, matchingSet.classRegExp, levels - ); - } else { - return _getParentElementWithNodeName( - node, matchingSet.nodeName, levels - ); - } - }; -})(); -/** - * Get element's style for a specific css property - * - * @param {Element} element The element on which to retrieve the style - * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...) - * - * @example - * wysihtml5.dom.getStyle("display").from(document.body); - * // => "block" - */ -wysihtml5.dom.getStyle = (function() { - var stylePropertyMapping = { - "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat" - }, - REG_EXP_CAMELIZE = /\-[a-z]/g; - - function camelize(str) { - return str.replace(REG_EXP_CAMELIZE, function(match) { - return match.charAt(1).toUpperCase(); - }); - } - - return function(property) { - return { - from: function(element) { - if (element.nodeType !== wysihtml5.ELEMENT_NODE) { - return; - } - - var doc = element.ownerDocument, - camelizedProperty = stylePropertyMapping[property] || camelize(property), - style = element.style, - currentStyle = element.currentStyle, - styleValue = style[camelizedProperty]; - if (styleValue) { - return styleValue; - } - - // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant - // window.getComputedStyle, since it returns css property values in their original unit: - // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle - // gives you the original "50%". - // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio - if (currentStyle) { - try { - return currentStyle[camelizedProperty]; - } catch(e) { - //ie will occasionally fail for unknown reasons. swallowing exception - } - } - - var win = doc.defaultView || doc.parentWindow, - needsOverflowReset = (property === "height" || property === "width") && element.nodeName === "TEXTAREA", - originalOverflow, - returnValue; - - if (win.getComputedStyle) { - // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars - // therfore we remove and restore the scrollbar and calculate the value in between - if (needsOverflowReset) { - originalOverflow = style.overflow; - style.overflow = "hidden"; - } - returnValue = win.getComputedStyle(element, null).getPropertyValue(property); - if (needsOverflowReset) { - style.overflow = originalOverflow || ""; - } - return returnValue; - } - } - }; - }; -})();/** - * High performant way to check whether an element with a specific tag name is in the given document - * Optimized for being heavily executed - * Unleashes the power of live node lists - * - * @param {Object} doc The document object of the context where to check - * @param {String} tagName Upper cased tag name - * @example - * wysihtml5.dom.hasElementWithTagName(document, "IMG"); - */ -wysihtml5.dom.hasElementWithTagName = (function() { - var LIVE_CACHE = {}, - DOCUMENT_IDENTIFIER = 1; - - function _getDocumentIdentifier(doc) { - return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++); - } - - return function(doc, tagName) { - var key = _getDocumentIdentifier(doc) + ":" + tagName, - cacheEntry = LIVE_CACHE[key]; - if (!cacheEntry) { - cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName); - } - - return cacheEntry.length > 0; - }; -})();/** - * High performant way to check whether an element with a specific class name is in the given document - * Optimized for being heavily executed - * Unleashes the power of live node lists - * - * @param {Object} doc The document object of the context where to check - * @param {String} tagName Upper cased tag name - * @example - * wysihtml5.dom.hasElementWithClassName(document, "foobar"); - */ -(function(wysihtml5) { - var LIVE_CACHE = {}, - DOCUMENT_IDENTIFIER = 1; - - function _getDocumentIdentifier(doc) { - return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++); - } - - wysihtml5.dom.hasElementWithClassName = function(doc, className) { - // getElementsByClassName is not supported by IE<9 - // but is sometimes mocked via library code (which then doesn't return live node lists) - if (!wysihtml5.browser.supportsNativeGetElementsByClassName()) { - return !!doc.querySelector("." + className); - } - - var key = _getDocumentIdentifier(doc) + ":" + className, - cacheEntry = LIVE_CACHE[key]; - if (!cacheEntry) { - cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className); - } - - return cacheEntry.length > 0; - }; -})(wysihtml5); -wysihtml5.dom.insert = function(elementToInsert) { - return { - after: function(element) { - element.parentNode.insertBefore(elementToInsert, element.nextSibling); - }, - - before: function(element) { - element.parentNode.insertBefore(elementToInsert, element); - }, - - into: function(element) { - element.appendChild(elementToInsert); - } - }; -};wysihtml5.dom.insertCSS = function(rules) { - rules = rules.join("\n"); - - return { - into: function(doc) { - var head = doc.head || doc.getElementsByTagName("head")[0], - styleElement = doc.createElement("style"); - - styleElement.type = "text/css"; - - if (styleElement.styleSheet) { - styleElement.styleSheet.cssText = rules; - } else { - styleElement.appendChild(doc.createTextNode(rules)); - } - - if (head) { - head.appendChild(styleElement); - } - } - }; -};/** - * Method to set dom events - * - * @example - * wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... }); - */ -wysihtml5.dom.observe = function(element, eventNames, handler) { - eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames; - - var handlerWrapper, - eventName, - i = 0, - length = eventNames.length; - - for (; i<length; i++) { - eventName = eventNames[i]; - if (element.addEventListener) { - element.addEventListener(eventName, handler, false); - } else { - handlerWrapper = function(event) { - if (!("target" in event)) { - event.target = event.srcElement; - } - event.preventDefault = event.preventDefault || function() { - this.returnValue = false; - }; - event.stopPropagation = event.stopPropagation || function() { - this.cancelBubble = true; - }; - handler.call(element, event); - }; - element.attachEvent("on" + eventName, handlerWrapper); - } - } - - return { - stop: function() { - var eventName, - i = 0, - length = eventNames.length; - for (; i<length; i++) { - eventName = eventNames[i]; - if (element.removeEventListener) { - element.removeEventListener(eventName, handler, false); - } else { - element.detachEvent("on" + eventName, handlerWrapper); - } - } - } - }; -}; -/** - * HTML Sanitizer - * Rewrites the HTML based on given rules - * - * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized - * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will - * be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the - * desired substitution. - * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing - * - * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element. - * - * @example - * var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>'; - * wysihtml5.dom.parse(userHTML, { - * tags { - * p: "div", // Rename p tags to div tags - * font: "span" // Rename font tags to span tags - * div: true, // Keep them, also possible (same result when passing: "div" or true) - * script: undefined // Remove script elements - * } - * }); - * // => <div><div><span>foo bar</span></div></div> - * - * var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>'; - * wysihtml5.dom.parse(userHTML); - * // => '<span><span><span><span>I'm a table!</span></span></span></span>' - * - * var userHTML = '<div>foobar<br>foobar</div>'; - * wysihtml5.dom.parse(userHTML, { - * tags: { - * div: undefined, - * br: true - * } - * }); - * // => '' - * - * var userHTML = '<div class="red">foo</div><div class="pink">bar</div>'; - * wysihtml5.dom.parse(userHTML, { - * classes: { - * red: 1, - * green: 1 - * }, - * tags: { - * div: { - * rename_tag: "p" - * } - * } - * }); - * // => '<p class="red">foo</p><p>bar</p>' - */ -wysihtml5.dom.parse = (function() { - - /** - * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML - * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the - * node isn't closed - * - * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML. - */ - var NODE_TYPE_MAPPING = { - "1": _handleElement, - "3": _handleText - }, - // Rename unknown tags to this - DEFAULT_NODE_NAME = "span", - WHITE_SPACE_REG_EXP = /\s+/, - defaultRules = { tags: {}, classes: {} }, - currentRules = {}; - - /** - * Iterates over all childs of the element, recreates them, appends them into a document fragment - * which later replaces the entire body content - */ - function parse(elementOrHtml, rules, context, cleanUp) { - wysihtml5.lang.object(currentRules).merge(defaultRules).merge(rules).get(); - - context = context || elementOrHtml.ownerDocument || document; - var fragment = context.createDocumentFragment(), - isString = typeof(elementOrHtml) === "string", - element, - newNode, - firstChild; - - if (isString) { - element = wysihtml5.dom.getAsDom(elementOrHtml, context); - } else { - element = elementOrHtml; - } - - while (element.firstChild) { - firstChild = element.firstChild; - element.removeChild(firstChild); - newNode = _convert(firstChild, cleanUp); - if (newNode) { - fragment.appendChild(newNode); - } - } - - // Clear element contents - element.innerHTML = ""; - - // Insert new DOM tree - element.appendChild(fragment); - - return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element; - } - - function _convert(oldNode, cleanUp) { - var oldNodeType = oldNode.nodeType, - oldChilds = oldNode.childNodes, - oldChildsLength = oldChilds.length, - newNode, - method = NODE_TYPE_MAPPING[oldNodeType], - i = 0; - - newNode = method && method(oldNode); - - if (!newNode) { - return null; - } - - for (i=0; i<oldChildsLength; i++) { - newChild = _convert(oldChilds[i], cleanUp); - if (newChild) { - newNode.appendChild(newChild); - } - } - - // Cleanup senseless <span> elements - if (cleanUp && - newNode.childNodes.length <= 1 && - newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME && - !newNode.attributes.length) { - return newNode.firstChild; - } - - return newNode; - } - - function _handleElement(oldNode) { - var rule, - newNode, - endTag, - tagRules = currentRules.tags, - nodeName = oldNode.nodeName.toLowerCase(), - scopeName = oldNode.scopeName; - - /** - * We already parsed that element - * ignore it! (yes, this sometimes happens in IE8 when the html is invalid) - */ - if (oldNode._wysihtml5) { - return null; - } - oldNode._wysihtml5 = 1; - - if (oldNode.className === "wysihtml5-temp") { - return null; - } - - /** - * IE is the only browser who doesn't include the namespace in the - * nodeName, that's why we have to prepend it by ourselves - * scopeName is a proprietary IE feature - * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx - */ - if (scopeName && scopeName != "HTML") { - nodeName = scopeName + ":" + nodeName; - } - - /** - * Repair node - * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags - * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout - */ - if ("outerHTML" in oldNode) { - if (!wysihtml5.browser.autoClosesUnclosedTags() && - oldNode.nodeName === "P" && - oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") { - nodeName = "div"; - } - } - - if (nodeName in tagRules) { - rule = tagRules[nodeName]; - if (!rule || rule.remove) { - return null; - } - - rule = typeof(rule) === "string" ? { rename_tag: rule } : rule; - } else if (oldNode.firstChild) { - rule = { rename_tag: DEFAULT_NODE_NAME }; - } else { - // Remove empty unknown elements - return null; - } - - newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName); - _handleAttributes(oldNode, newNode, rule); - - oldNode = null; - return newNode; - } - - function _handleAttributes(oldNode, newNode, rule) { - var attributes = {}, // fresh new set of attributes to set on newNode - setClass = rule.set_class, // classes to set - addClass = rule.add_class, // add classes based on existing attributes - setAttributes = rule.set_attributes, // attributes to set on the current node - checkAttributes = rule.check_attributes, // check/convert values of attributes - allowedClasses = currentRules.classes, - i = 0, - classes = [], - newClasses = [], - newUniqueClasses = [], - oldClasses = [], - classesLength, - newClassesLength, - currentClass, - newClass, - attributeName, - newAttributeValue, - method; - - if (setAttributes) { - attributes = wysihtml5.lang.object(setAttributes).clone(); - } - - if (checkAttributes) { - for (attributeName in checkAttributes) { - method = attributeCheckMethods[checkAttributes[attributeName]]; - if (!method) { - continue; - } - newAttributeValue = method(_getAttribute(oldNode, attributeName)); - if (typeof(newAttributeValue) === "string") { - attributes[attributeName] = newAttributeValue; - } - } - } - - if (setClass) { - classes.push(setClass); - } - - if (addClass) { - for (attributeName in addClass) { - method = addClassMethods[addClass[attributeName]]; - if (!method) { - continue; - } - newClass = method(_getAttribute(oldNode, attributeName)); - if (typeof(newClass) === "string") { - classes.push(newClass); - } - } - } - - // make sure that wysihtml5 temp class doesn't get stripped out - allowedClasses["_wysihtml5-temp-placeholder"] = 1; - - // add old classes last - oldClasses = oldNode.getAttribute("class"); - if (oldClasses) { - classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP)); - } - classesLength = classes.length; - for (; i<classesLength; i++) { - currentClass = classes[i]; - if (allowedClasses[currentClass]) { - newClasses.push(currentClass); - } - } - - // remove duplicate entries and preserve class specificity - newClassesLength = newClasses.length; - while (newClassesLength--) { - currentClass = newClasses[newClassesLength]; - if (!wysihtml5.lang.array(newUniqueClasses).contains(currentClass)) { - newUniqueClasses.unshift(currentClass); - } - } - - if (newUniqueClasses.length) { - attributes["class"] = newUniqueClasses.join(" "); - } - - // set attributes on newNode - for (attributeName in attributes) { - // Setting attributes can cause a js error in IE under certain circumstances - // eg. on a <img> under https when it's new attribute value is non-https - // TODO: Investigate this further and check for smarter handling - try { - newNode.setAttribute(attributeName, attributes[attributeName]); - } catch(e) {} - } - - // IE8 sometimes loses the width/height attributes when those are set before the "src" - // so we make sure to set them again - if (attributes.src) { - if (typeof(attributes.width) !== "undefined") { - newNode.setAttribute("width", attributes.width); - } - if (typeof(attributes.height) !== "undefined") { - newNode.setAttribute("height", attributes.height); - } - } - } - - /** - * IE gives wrong results for hasAttribute/getAttribute, for example: - * var td = document.createElement("td"); - * td.getAttribute("rowspan"); // => "1" in IE - * - * Therefore we have to check the element's outerHTML for the attribute - */ - var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly(); - function _getAttribute(node, attributeName) { - attributeName = attributeName.toLowerCase(); - var nodeName = node.nodeName; - if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) { - // Get 'src' attribute value via object property since this will always contain the - // full absolute url (http://...) - // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host - // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url) - return node.src; - } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) { - // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML - var outerHTML = node.outerHTML.toLowerCase(), - // TODO: This might not work for attributes without value: <input disabled> - hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1; - - return hasAttribute ? node.getAttribute(attributeName) : null; - } else{ - return node.getAttribute(attributeName); - } - } - - /** - * Check whether the given node is a proper loaded image - * FIXME: Returns undefined when unknown (Chrome, Safari) - */ - function _isLoadedImage(node) { - try { - return node.complete && !node.mozMatchesSelector(":-moz-broken"); - } catch(e) { - if (node.complete && node.readyState === "complete") { - return true; - } - } - } - - function _handleText(oldNode) { - return oldNode.ownerDocument.createTextNode(oldNode.data); - } - - - // ------------ attribute checks ------------ \\ - var attributeCheckMethods = { - url: (function() { - var REG_EXP = /^https?:\/\//i; - return function(attributeValue) { - if (!attributeValue || !attributeValue.match(REG_EXP)) { - return null; - } - return attributeValue.replace(REG_EXP, function(match) { - return match.toLowerCase(); - }); - }; - })(), - - alt: (function() { - var REG_EXP = /[^ a-z0-9_\-]/gi; - return function(attributeValue) { - if (!attributeValue) { - return ""; - } - return attributeValue.replace(REG_EXP, ""); - }; - })(), - - numbers: (function() { - var REG_EXP = /\D/g; - return function(attributeValue) { - attributeValue = (attributeValue || "").replace(REG_EXP, ""); - return attributeValue || null; - }; - })() - }; - - // ------------ class converter (converts an html attribute to a class name) ------------ \\ - var addClassMethods = { - align_img: (function() { - var mapping = { - left: "wysiwyg-float-left", - right: "wysiwyg-float-right" - }; - return function(attributeValue) { - return mapping[String(attributeValue).toLowerCase()]; - }; - })(), - - align_text: (function() { - var mapping = { - left: "wysiwyg-text-align-left", - right: "wysiwyg-text-align-right", - center: "wysiwyg-text-align-center", - justify: "wysiwyg-text-align-justify" - }; - return function(attributeValue) { - return mapping[String(attributeValue).toLowerCase()]; - }; - })(), - - clear_br: (function() { - var mapping = { - left: "wysiwyg-clear-left", - right: "wysiwyg-clear-right", - both: "wysiwyg-clear-both", - all: "wysiwyg-clear-both" - }; - return function(attributeValue) { - return mapping[String(attributeValue).toLowerCase()]; - }; - })(), - - size_font: (function() { - var mapping = { - "1": "wysiwyg-font-size-xx-small", - "2": "wysiwyg-font-size-small", - "3": "wysiwyg-font-size-medium", - "4": "wysiwyg-font-size-large", - "5": "wysiwyg-font-size-x-large", - "6": "wysiwyg-font-size-xx-large", - "7": "wysiwyg-font-size-xx-large", - "-": "wysiwyg-font-size-smaller", - "+": "wysiwyg-font-size-larger" - }; - return function(attributeValue) { - return mapping[String(attributeValue).charAt(0)]; - }; - })() - }; - - return parse; -})();/** - * Checks for empty text node childs and removes them - * - * @param {Element} node The element in which to cleanup - * @example - * wysihtml5.dom.removeEmptyTextNodes(element); - */ -wysihtml5.dom.removeEmptyTextNodes = function(node) { - var childNode, - childNodes = wysihtml5.lang.array(node.childNodes).get(), - childNodesLength = childNodes.length, - i = 0; - for (; i<childNodesLength; i++) { - childNode = childNodes[i]; - if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") { - childNode.parentNode.removeChild(childNode); - } - } -}; -/** - * Renames an element (eg. a <div> to a <p>) and keeps its childs - * - * @param {Element} element The list element which should be renamed - * @param {Element} newNodeName The desired tag name - * - * @example - * <!-- Assume the following dom: --> - * <ul id="list"> - * <li>eminem</li> - * <li>dr. dre</li> - * <li>50 Cent</li> - * </ul> - * - * <script> - * wysihtml5.dom.renameElement(document.getElementById("list"), "ol"); - * </script> - * - * <!-- Will result in: --> - * <ol> - * <li>eminem</li> - * <li>dr. dre</li> - * <li>50 Cent</li> - * </ol> - */ -wysihtml5.dom.renameElement = function(element, newNodeName) { - var newElement = element.ownerDocument.createElement(newNodeName), - firstChild; - while (firstChild = element.firstChild) { - newElement.appendChild(firstChild); - } - wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement); - element.parentNode.replaceChild(newElement, element); - return newElement; -};/** - * Takes an element, removes it and replaces it with it's childs - * - * @param {Object} node The node which to replace with it's child nodes - * @example - * <div id="foo"> - * <span>hello</span> - * </div> - * <script> - * // Remove #foo and replace with it's children - * wysihtml5.dom.replaceWithChildNodes(document.getElementById("foo")); - * </script> - */ -wysihtml5.dom.replaceWithChildNodes = function(node) { - if (!node.parentNode) { - return; - } - - if (!node.firstChild) { - node.parentNode.removeChild(node); - return; - } - - var fragment = node.ownerDocument.createDocumentFragment(); - while (node.firstChild) { - fragment.appendChild(node.firstChild); - } - node.parentNode.replaceChild(fragment, node); - node = fragment = null; -}; -/** - * Unwraps an unordered/ordered list - * - * @param {Element} element The list element which should be unwrapped - * - * @example - * <!-- Assume the following dom: --> - * <ul id="list"> - * <li>eminem</li> - * <li>dr. dre</li> - * <li>50 Cent</li> - * </ul> - * - * <script> - * wysihtml5.dom.resolveList(document.getElementById("list")); - * </script> - * - * <!-- Will result in: --> - * eminem<br> - * dr. dre<br> - * 50 Cent<br> - */ -(function(dom) { - function _isBlockElement(node) { - return dom.getStyle("display").from(node) === "block"; - } - - function _isLineBreak(node) { - return node.nodeName === "BR"; - } - - function _appendLineBreak(element) { - var lineBreak = element.ownerDocument.createElement("br"); - element.appendChild(lineBreak); - } - - function resolveList(list) { - if (list.nodeName !== "MENU" && list.nodeName !== "UL" && list.nodeName !== "OL") { - return; - } - - var doc = list.ownerDocument, - fragment = doc.createDocumentFragment(), - previousSibling = list.previousElementSibling || list.previousSibling, - firstChild, - lastChild, - isLastChild, - shouldAppendLineBreak, - listItem; - - if (previousSibling && !_isBlockElement(previousSibling)) { - _appendLineBreak(fragment); - } - - while (listItem = list.firstChild) { - lastChild = listItem.lastChild; - while (firstChild = listItem.firstChild) { - isLastChild = firstChild === lastChild; - // This needs to be done before appending it to the fragment, as it otherwise will loose style information - shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild); - fragment.appendChild(firstChild); - if (shouldAppendLineBreak) { - _appendLineBreak(fragment); - } - } - - listItem.parentNode.removeChild(listItem); - } - list.parentNode.replaceChild(fragment, list); - } - - dom.resolveList = resolveList; -})(wysihtml5.dom);/** - * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way - * - * Browser Compatibility: - * - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted" - * - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...) - * - * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons: - * - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'") - * - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...) - * - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire - * - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe - * can do anything as if the sandbox attribute wasn't set - * - * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready - * @param {Object} [config] Optional parameters - * - * @example - * new wysihtml5.dom.Sandbox(function(sandbox) { - * sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">'; - * }); - */ -(function(wysihtml5) { - var /** - * Default configuration - */ - doc = document, - /** - * Properties to unset/protect on the window object - */ - windowProperties = [ - "parent", "top", "opener", "frameElement", "frames", - "localStorage", "globalStorage", "sessionStorage", "indexedDB" - ], - /** - * Properties on the window object which are set to an empty function - */ - windowProperties2 = [ - "open", "close", "openDialog", "showModalDialog", - "alert", "confirm", "prompt", - "openDatabase", "postMessage", - "XMLHttpRequest", "XDomainRequest" - ], - /** - * Properties to unset/protect on the document object - */ - documentProperties = [ - "referrer", - "write", "open", "close" - ]; - - wysihtml5.dom.Sandbox = Base.extend( - /** @scope wysihtml5.dom.Sandbox.prototype */ { - - constructor: function(readyCallback, config) { - this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION; - this.config = wysihtml5.lang.object({}).merge(config).get(); - this.iframe = this._createIframe(); - }, - - insertInto: function(element) { - if (typeof(element) === "string") { - element = doc.getElementById(element); - } - - element.appendChild(this.iframe); - }, - - getIframe: function() { - return this.iframe; - }, - - getWindow: function() { - this._readyError(); - }, - - getDocument: function() { - this._readyError(); - }, - - destroy: function() { - var iframe = this.getIframe(); - iframe.parentNode.removeChild(iframe); - }, - - _readyError: function() { - throw new Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet"); - }, - - /** - * Creates the sandbox iframe - * - * Some important notes: - * - We can't use HTML5 sandbox for now: - * setting it causes that the iframe's dom can't be accessed from the outside - * Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom - * But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired. - * In order to make this happen we need to set the "allow-scripts" flag. - * A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all. - * - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document) - * - IE needs to have the security="restricted" attribute set before the iframe is - * inserted into the dom tree - * - Believe it or not but in IE "security" in document.createElement("iframe") is false, even - * though it supports it - * - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore - * - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely - * on the onreadystatechange event - */ - _createIframe: function() { - var that = this, - iframe = doc.createElement("iframe"); - iframe.className = "wysihtml5-sandbox"; - wysihtml5.dom.setAttributes({ - "security": "restricted", - "allowtransparency": "true", - "frameborder": 0, - "width": 0, - "height": 0, - "marginwidth": 0, - "marginheight": 0 - }).on(iframe); - - // Setting the src like this prevents ssl warnings in IE6 - if (wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) { - iframe.src = "javascript:'<html></html>'"; - } - - iframe.onload = function() { - iframe.onreadystatechange = iframe.onload = null; - that._onLoadIframe(iframe); - }; - - iframe.onreadystatechange = function() { - if (/loaded|complete/.test(iframe.readyState)) { - iframe.onreadystatechange = iframe.onload = null; - that._onLoadIframe(iframe); - } - }; - - return iframe; - }, - - /** - * Callback for when the iframe has finished loading - */ - _onLoadIframe: function(iframe) { - // don't resume when the iframe got unloaded (eg. by removing it from the dom) - if (!wysihtml5.dom.contains(doc.documentElement, iframe)) { - return; - } - - var that = this, - iframeWindow = iframe.contentWindow, - iframeDocument = iframe.contentWindow.document, - charset = doc.characterSet || doc.charset || "utf-8", - sandboxHtml = this._getHtml({ - charset: charset, - stylesheets: this.config.stylesheets - }); - - // Create the basic dom tree including proper DOCTYPE and charset - iframeDocument.open("text/html", "replace"); - iframeDocument.write(sandboxHtml); - iframeDocument.close(); - - this.getWindow = function() { return iframe.contentWindow; }; - this.getDocument = function() { return iframe.contentWindow.document; }; - - // Catch js errors and pass them to the parent's onerror event - // addEventListener("error") doesn't work properly in some browsers - // TODO: apparently this doesn't work in IE9! - iframeWindow.onerror = function(errorMessage, fileName, lineNumber) { - throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber); - }; - - if (!wysihtml5.browser.supportsSandboxedIframes()) { - // Unset a bunch of sensitive variables - // Please note: This isn't hack safe! - // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information - // IE is secure though, which is the most important thing, since IE is the only browser, who - // takes over scripts & styles into contentEditable elements when copied from external websites - // or applications (Microsoft Word, ...) - var i, length; - for (i=0, length=windowProperties.length; i<length; i++) { - this._unset(iframeWindow, windowProperties[i]); - } - for (i=0, length=windowProperties2.length; i<length; i++) { - this._unset(iframeWindow, windowProperties2[i], wysihtml5.EMPTY_FUNCTION); - } - for (i=0, length=documentProperties.length; i<length; i++) { - this._unset(iframeDocument, documentProperties[i]); - } - // This doesn't work in Safari 5 - // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit - this._unset(iframeDocument, "cookie", "", true); - } - - this.loaded = true; - - // Trigger the callback - setTimeout(function() { that.callback(that); }, 0); - }, - - _getHtml: function(templateVars) { - var stylesheets = templateVars.stylesheets, - html = "", - i = 0, - length; - stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets; - if (stylesheets) { - length = stylesheets.length; - for (; i<length; i++) { - html += '<link rel="stylesheet" href="' + stylesheets[i] + '">'; - } - } - templateVars.stylesheets = html; - - return wysihtml5.lang.string( - '<!DOCTYPE html><html><head>' - + '<meta charset="#{charset}">#{stylesheets}</head>' - + '<body></body></html>' - ).interpolate(templateVars); - }, - - /** - * Method to unset/override existing variables - * @example - * // Make cookie unreadable and unwritable - * this._unset(document, "cookie", "", true); - */ - _unset: function(object, property, value, setter) { - try { object[property] = value; } catch(e) {} - - try { object.__defineGetter__(property, function() { return value; }); } catch(e) {} - if (setter) { - try { object.__defineSetter__(property, function() {}); } catch(e) {} - } - - if (!wysihtml5.browser.crashesWhenDefineProperty(property)) { - try { - var config = { - get: function() { return value; } - }; - if (setter) { - config.set = function() {}; - } - Object.defineProperty(object, property, config); - } catch(e) {} - } - } - }); -})(wysihtml5); -(function() { - var mapping = { - "className": "class" - }; - wysihtml5.dom.setAttributes = function(attributes) { - return { - on: function(element) { - for (var i in attributes) { - element.setAttribute(mapping[i] || i, attributes[i]); - } - } - } - }; -})();wysihtml5.dom.setStyles = function(styles) { - return { - on: function(element) { - var style = element.style; - if (typeof(styles) === "string") { - style.cssText += ";" + styles; - return; - } - for (var i in styles) { - if (i === "float") { - style.cssFloat = styles[i]; - style.styleFloat = styles[i]; - } else { - style[i] = styles[i]; - } - } - } - }; -};/** - * Simulate HTML5 placeholder attribute - * - * Needed since - * - div[contentEditable] elements don't support it - * - older browsers (such as IE8 and Firefox 3.6) don't support it at all - * - * @param {Object} parent Instance of main wysihtml5.Editor class - * @param {Element} view Instance of wysihtml5.views.* class - * @param {String} placeholderText - * - * @example - * wysihtml.dom.simulatePlaceholder(this, composer, "Foobar"); - */ -(function(dom) { - dom.simulatePlaceholder = function(editor, view, placeholderText) { - var CLASS_NAME = "placeholder", - unset = function() { - if (view.hasPlaceholderSet()) { - view.clear(); - } - dom.removeClass(view.element, CLASS_NAME); - }, - set = function() { - if (view.isEmpty()) { - view.setValue(placeholderText); - dom.addClass(view.element, CLASS_NAME); - } - }; - - editor - .observe("set_placeholder", set) - .observe("unset_placeholder", unset) - .observe("focus:composer", unset) - .observe("paste:composer", unset) - .observe("blur:composer", set); - - set(); - }; -})(wysihtml5.dom); -(function(dom) { - var documentElement = document.documentElement; - if ("textContent" in documentElement) { - dom.setTextContent = function(element, text) { - element.textContent = text; - }; - - dom.getTextContent = function(element) { - return element.textContent; - }; - } else if ("innerText" in documentElement) { - dom.setTextContent = function(element, text) { - element.innerText = text; - }; - - dom.getTextContent = function(element) { - return element.innerText; - }; - } else { - dom.setTextContent = function(element, text) { - element.nodeValue = text; - }; - - dom.getTextContent = function(element) { - return element.nodeValue; - }; - } -})(wysihtml5.dom); - -/** - * Fix most common html formatting misbehaviors of browsers implementation when inserting - * content via copy & paste contentEditable - * - * @author Christopher Blum - */ -wysihtml5.quirks.cleanPastedHTML = (function() { - // TODO: We probably need more rules here - var defaultRules = { - // When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling - "a u": wysihtml5.dom.replaceWithChildNodes - }; - - function cleanPastedHTML(elementOrHtml, rules, context) { - rules = rules || defaultRules; - context = context || elementOrHtml.ownerDocument || document; - - var element, - isString = typeof(elementOrHtml) === "string", - method, - matches, - matchesLength, - i, - j = 0; - if (isString) { - element = wysihtml5.dom.getAsDom(elementOrHtml, context); - } else { - element = elementOrHtml; - } - - for (i in rules) { - matches = element.querySelectorAll(i); - method = rules[i]; - matchesLength = matches.length; - for (; j<matchesLength; j++) { - method(matches[j]); - } - } - - matches = elementOrHtml = rules = null; - - return isString ? element.innerHTML : element; - } - - return cleanPastedHTML; -})();/** - * IE and Opera leave an empty paragraph in the contentEditable element after clearing it - * - * @param {Object} contentEditableElement The contentEditable element to observe for clearing events - * @exaple - * wysihtml5.quirks.ensureProperClearing(myContentEditableElement); - */ -(function(wysihtml5) { - var dom = wysihtml5.dom; - - wysihtml5.quirks.ensureProperClearing = (function() { - var clearIfNecessary = function(event) { - var element = this; - setTimeout(function() { - var innerHTML = element.innerHTML.toLowerCase(); - if (innerHTML == "<p> </p>" || - innerHTML == "<p> </p><p> </p>") { - element.innerHTML = ""; - } - }, 0); - }; - - return function(composer) { - dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary); - }; - })(); - - - - /** - * In Opera when the caret is in the first and only item of a list (<ul><li>|</li></ul>) and the list is the first child of the contentEditable element, it's impossible to delete the list by hitting backspace - * - * @param {Object} contentEditableElement The contentEditable element to observe for clearing events - * @exaple - * wysihtml5.quirks.ensureProperClearing(myContentEditableElement); - */ - wysihtml5.quirks.ensureProperClearingOfLists = (function() { - var ELEMENTS_THAT_CONTAIN_LI = ["OL", "UL", "MENU"]; - - var clearIfNecessary = function(element, contentEditableElement) { - if (!contentEditableElement.firstChild || !wysihtml5.lang.array(ELEMENTS_THAT_CONTAIN_LI).contains(contentEditableElement.firstChild.nodeName)) { - return; - } - - var list = dom.getParentElement(element, { nodeName: ELEMENTS_THAT_CONTAIN_LI }); - if (!list) { - return; - } - - var listIsFirstChildOfContentEditable = list == contentEditableElement.firstChild; - if (!listIsFirstChildOfContentEditable) { - return; - } - - var hasOnlyOneListItem = list.childNodes.length <= 1; - if (!hasOnlyOneListItem) { - return; - } - - var onlyListItemIsEmpty = list.firstChild ? list.firstChild.innerHTML === "" : true; - if (!onlyListItemIsEmpty) { - return; - } - - list.parentNode.removeChild(list); - }; - - return function(composer) { - dom.observe(composer.element, "keydown", function(event) { - if (event.keyCode !== wysihtml5.BACKSPACE_KEY) { - return; - } - - var element = composer.selection.getSelectedNode(); - clearIfNecessary(element, composer.element); - }); - }; - })(); - -})(wysihtml5); -// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398 -// -// In Firefox this: -// var d = document.createElement("div"); -// d.innerHTML ='<a href="~"></a>'; -// d.innerHTML; -// will result in: -// <a href="%7E"></a> -// which is wrong -(function(wysihtml5) { - var TILDE_ESCAPED = "%7E"; - wysihtml5.quirks.getCorrectInnerHTML = function(element) { - var innerHTML = element.innerHTML; - if (innerHTML.indexOf(TILDE_ESCAPED) === -1) { - return innerHTML; - } - - var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"), - url, - urlToSearch, - length, - i; - for (i=0, length=elementsWithTilde.length; i<length; i++) { - url = elementsWithTilde[i].href || elementsWithTilde[i].src; - urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED); - innerHTML = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url); - } - return innerHTML; - }; -})(wysihtml5);/** - * Some browsers don't insert line breaks when hitting return in a contentEditable element - * - Opera & IE insert new <p> on return - * - Chrome & Safari insert new <div> on return - * - Firefox inserts <br> on return (yippie!) - * - * @param {Element} element - * - * @example - * wysihtml5.quirks.insertLineBreakOnReturn(element); - */ -(function(wysihtml5) { - var dom = wysihtml5.dom, - USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"], - LIST_TAGS = ["UL", "OL", "MENU"]; - - wysihtml5.quirks.insertLineBreakOnReturn = function(composer) { - function unwrap(selectedNode) { - var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2); - if (!parentElement) { - return; - } - - var invisibleSpace = document.createTextNode(wysihtml5.INVISIBLE_SPACE); - dom.insert(invisibleSpace).before(parentElement); - dom.replaceWithChildNodes(parentElement); - composer.selection.selectNode(invisibleSpace); - } - - function keyDown(event) { - var keyCode = event.keyCode; - if (event.shiftKey || (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY)) { - return; - } - - var element = event.target, - selectedNode = composer.selection.getSelectedNode(), - blockElement = dom.getParentElement(selectedNode, { nodeName: USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS }, 4); - if (blockElement) { - // Some browsers create <p> elements after leaving a list - // check after keydown of backspace and return whether a <p> got inserted and unwrap it - if (blockElement.nodeName === "LI" && (keyCode === wysihtml5.ENTER_KEY || keyCode === wysihtml5.BACKSPACE_KEY)) { - setTimeout(function() { - var selectedNode = composer.selection.getSelectedNode(), - list, - div; - if (!selectedNode) { - return; - } - - list = dom.getParentElement(selectedNode, { - nodeName: LIST_TAGS - }, 2); - - if (list) { - return; - } - - unwrap(selectedNode); - }, 0); - } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === wysihtml5.ENTER_KEY) { - setTimeout(function() { - unwrap(composer.selection.getSelectedNode()); - }, 0); - } - return; - } - - if (keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) { - composer.commands.exec("insertLineBreak"); - event.preventDefault(); - } - } - - // keypress doesn't fire when you hit backspace - dom.observe(composer.element.ownerDocument, "keydown", keyDown); - }; -})(wysihtml5);/** - * Force rerendering of a given element - * Needed to fix display misbehaviors of IE - * - * @param {Element} element The element object which needs to be rerendered - * @example - * wysihtml5.quirks.redraw(document.body); - */ -(function(wysihtml5) { - var CLASS_NAME = "wysihtml5-quirks-redraw"; - - wysihtml5.quirks.redraw = function(element) { - wysihtml5.dom.addClass(element, CLASS_NAME); - wysihtml5.dom.removeClass(element, CLASS_NAME); - - // Following hack is needed for firefox to make sure that image resize handles are properly removed - try { - var doc = element.ownerDocument; - doc.execCommand("italic", false, null); - doc.execCommand("italic", false, null); - } catch(e) {} - }; -})(wysihtml5);/** - * Selection API - * - * @example - * var selection = new wysihtml5.Selection(editor); - */ -(function(wysihtml5) { - var dom = wysihtml5.dom; - - function _getCumulativeOffsetTop(element) { - var top = 0; - if (element.parentNode) { - do { - top += element.offsetTop || 0; - element = element.offsetParent; - } while (element); - } - return top; - } - - wysihtml5.Selection = Base.extend( - /** @scope wysihtml5.Selection.prototype */ { - constructor: function(editor) { - // Make sure that our external range library is initialized - window.rangy.init(); - - this.editor = editor; - this.composer = editor.composer; - this.doc = this.composer.doc; - }, - - /** - * Get the current selection as a bookmark to be able to later restore it - * - * @return {Object} An object that represents the current selection - */ - getBookmark: function() { - var range = this.getRange(); - return range && range.cloneRange(); - }, - - /** - * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark - * - * @param {Object} bookmark An object that represents the current selection - */ - setBookmark: function(bookmark) { - if (!bookmark) { - return; - } - - this.setSelection(bookmark); - }, - - /** - * Set the caret in front of the given node - * - * @param {Object} node The element or text node where to position the caret in front of - * @example - * selection.setBefore(myElement); - */ - setBefore: function(node) { - var range = rangy.createRange(this.doc); - range.setStartBefore(node); - range.setEndBefore(node); - return this.setSelection(range); - }, - - /** - * Set the caret after the given node - * - * @param {Object} node The element or text node where to position the caret in front of - * @example - * selection.setBefore(myElement); - */ - setAfter: function(node) { - var range = rangy.createRange(this.doc); - range.setStartAfter(node); - range.setEndAfter(node); - return this.setSelection(range); - }, - - /** - * Ability to select/mark nodes - * - * @param {Element} node The node/element to select - * @example - * selection.selectNode(document.getElementById("my-image")); - */ - selectNode: function(node) { - var range = rangy.createRange(this.doc), - isElement = node.nodeType === wysihtml5.ELEMENT_NODE, - canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"), - content = isElement ? node.innerHTML : node.data, - isEmpty = (content === "" || content === wysihtml5.INVISIBLE_SPACE), - displayStyle = dom.getStyle("display").from(node), - isBlockElement = (displayStyle === "block" || displayStyle === "list-item"); - - if (isEmpty && isElement && canHaveHTML) { - // Make sure that caret is visible in node by inserting a zero width no breaking space - try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {} - } - - if (canHaveHTML) { - range.selectNodeContents(node); - } else { - range.selectNode(node); - } - - if (canHaveHTML && isEmpty && isElement) { - range.collapse(isBlockElement); - } else if (canHaveHTML && isEmpty) { - range.setStartAfter(node); - range.setEndAfter(node); - } - - this.setSelection(range); - }, - - /** - * Get the node which contains the selection - * - * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange" - * @return {Object} The node that contains the caret - * @example - * var nodeThatContainsCaret = selection.getSelectedNode(); - */ - getSelectedNode: function(controlRange) { - var selection, - range; - - if (controlRange && this.doc.selection && this.doc.selection.type === "Control") { - range = this.doc.selection.createRange(); - if (range && range.length) { - return range.item(0); - } - } - - selection = this.getSelection(this.doc); - if (selection.focusNode === selection.anchorNode) { - return selection.focusNode; - } else { - range = this.getRange(this.doc); - return range ? range.commonAncestorContainer : this.doc.body; - } - }, - - executeAndRestore: function(method, restoreScrollPosition) { - var body = this.doc.body, - oldScrollTop = restoreScrollPosition && body.scrollTop, - oldScrollLeft = restoreScrollPosition && body.scrollLeft, - className = "_wysihtml5-temp-placeholder", - placeholderHTML = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>', - range = this.getRange(this.doc), - newRange; - - // Nothing selected, execute and say goodbye - if (!range) { - method(body, body); - return; - } - - var node = range.createContextualFragment(placeholderHTML); - range.insertNode(node); - - // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder - try { - method(range.startContainer, range.endContainer); - } catch(e3) { - setTimeout(function() { throw e3; }, 0); - } - - caretPlaceholder = this.doc.querySelector("." + className); - if (caretPlaceholder) { - newRange = rangy.createRange(this.doc); - newRange.selectNode(caretPlaceholder); - newRange.deleteContents(); - this.setSelection(newRange); - } else { - // fallback for when all hell breaks loose - body.focus(); - } - - if (restoreScrollPosition) { - body.scrollTop = oldScrollTop; - body.scrollLeft = oldScrollLeft; - } - - // Remove it again, just to make sure that the placeholder is definitely out of the dom tree - try { - caretPlaceholder.parentNode.removeChild(caretPlaceholder); - } catch(e4) {} - }, - - /** - * Different approach of preserving the selection (doesn't modify the dom) - * Takes all text nodes in the selection and saves the selection position in the first and last one - */ - executeAndRestoreSimple: function(method) { - var range = this.getRange(), - body = this.doc.body, - newRange, - firstNode, - lastNode, - textNodes, - rangeBackup; - - // Nothing selected, execute and say goodbye - if (!range) { - method(body, body); - return; - } - - textNodes = range.getNodes([3]); - firstNode = textNodes[0] || range.startContainer; - lastNode = textNodes[textNodes.length - 1] || range.endContainer; - - rangeBackup = { - collapsed: range.collapsed, - startContainer: firstNode, - startOffset: firstNode === range.startContainer ? range.startOffset : 0, - endContainer: lastNode, - endOffset: lastNode === range.endContainer ? range.endOffset : lastNode.length - }; - - try { - method(range.startContainer, range.endContainer); - } catch(e) { - setTimeout(function() { throw e; }, 0); - } - - newRange = rangy.createRange(this.doc); - try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {} - try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {} - try { this.setSelection(newRange); } catch(e3) {} - }, - - /** - * Insert html at the caret position and move the cursor after the inserted html - * - * @param {String} html HTML string to insert - * @example - * selection.insertHTML("<p>foobar</p>"); - */ - insertHTML: function(html) { - var range = rangy.createRange(this.doc), - node = range.createContextualFragment(html), - lastChild = node.lastChild; - this.insertNode(node); - if (lastChild) { - this.setAfter(lastChild); - } - }, - - /** - * Insert a node at the caret position and move the cursor behind it - * - * @param {Object} node HTML string to insert - * @example - * selection.insertNode(document.createTextNode("foobar")); - */ - insertNode: function(node) { - var range = this.getRange(); - if (range) { - range.insertNode(node); - } - }, - - /** - * Wraps current selection with the given node - * - * @param {Object} node The node to surround the selected elements with - */ - surround: function(node) { - var range = this.getRange(); - if (!range) { - return; - } - - try { - // This only works when the range boundaries are not overlapping other elements - range.surroundContents(node); - this.selectNode(node); - } catch(e) { - // fallback - node.appendChild(range.extractContents()); - range.insertNode(node); - } - }, - - /** - * Scroll the current caret position into the view - * FIXME: This is a bit hacky, there might be a smarter way of doing this - * - * @example - * selection.scrollIntoView(); - */ - scrollIntoView: function() { - var doc = this.doc, - hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight, - tempElement = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() { - var element = doc.createElement("span"); - // The element needs content in order to be able to calculate it's position properly - element.innerHTML = wysihtml5.INVISIBLE_SPACE; - return element; - })(), - offsetTop; - - if (hasScrollBars) { - this.insertNode(tempElement); - offsetTop = _getCumulativeOffsetTop(tempElement); - tempElement.parentNode.removeChild(tempElement); - if (offsetTop > doc.body.scrollTop) { - doc.body.scrollTop = offsetTop; - } - } - }, - - /** - * Select line where the caret is in - */ - selectLine: function() { - if (wysihtml5.browser.supportsSelectionModify()) { - this._selectLine_W3C(); - } else if (this.doc.selection) { - this._selectLine_MSIE(); - } - }, - - /** - * See https://developer.mozilla.org/en/DOM/Selection/modify - */ - _selectLine_W3C: function() { - var win = this.doc.defaultView, - selection = win.getSelection(); - selection.modify("extend", "left", "lineboundary"); - selection.modify("extend", "right", "lineboundary"); - }, - - _selectLine_MSIE: function() { - var range = this.doc.selection.createRange(), - rangeTop = range.boundingTop, - rangeHeight = range.boundingHeight, - scrollWidth = this.doc.body.scrollWidth, - rangeBottom, - rangeEnd, - measureNode, - i, - j; - - if (!range.moveToPoint) { - return; - } - - if (rangeTop === 0) { - // Don't know why, but when the selection ends at the end of a line - // range.boundingTop is 0 - measureNode = this.doc.createElement("span"); - this.insertNode(measureNode); - rangeTop = measureNode.offsetTop; - measureNode.parentNode.removeChild(measureNode); - } - - rangeTop += 1; - - for (i=-10; i<scrollWidth; i+=2) { - try { - range.moveToPoint(i, rangeTop); - break; - } catch(e1) {} - } - - // Investigate the following in order to handle multi line selections - // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0); - rangeBottom = rangeTop; - rangeEnd = this.doc.selection.createRange(); - for (j=scrollWidth; j>=0; j--) { - try { - rangeEnd.moveToPoint(j, rangeBottom); - break; - } catch(e2) {} - } - - range.setEndPoint("EndToEnd", rangeEnd); - range.select(); - }, - - getText: function() { - var selection = this.getSelection(); - return selection ? selection.toString() : ""; - }, - - getNodes: function(nodeType, filter) { - var range = this.getRange(); - if (range) { - return range.getNodes([nodeType], filter); - } else { - return []; - } - }, - - getRange: function() { - var selection = this.getSelection(); - return selection && selection.rangeCount && selection.getRangeAt(0); - }, - - getSelection: function() { - return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow); - }, - - setSelection: function(range) { - var win = this.doc.defaultView || this.doc.parentWindow, - selection = rangy.getSelection(win); - return selection.setSingleRange(range); - } - }); - -})(wysihtml5); -/** - * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license. - * http://code.google.com/p/rangy/ - * - * changed in order to be able ... - * - to use custom tags - * - to detect and replace similar css classes via reg exp - */ -(function(wysihtml5, rangy) { - var defaultTagName = "span"; - - var REG_EXP_WHITE_SPACE = /\s+/g; - - function hasClass(el, cssClass, regExp) { - if (!el.className) { - return false; - } - - var matchingClassNames = el.className.match(regExp) || []; - return matchingClassNames[matchingClassNames.length - 1] === cssClass; - } - - function addClass(el, cssClass, regExp) { - if (el.className) { - removeClass(el, regExp); - el.className += " " + cssClass; - } else { - el.className = cssClass; - } - } - - function removeClass(el, regExp) { - if (el.className) { - el.className = el.className.replace(regExp, ""); - } - } - - function hasSameClasses(el1, el2) { - return el1.className.replace(REG_EXP_WHITE_SPACE, " ") == el2.className.replace(REG_EXP_WHITE_SPACE, " "); - } - - function replaceWithOwnChildren(el) { - var parent = el.parentNode; - while (el.firstChild) { - parent.insertBefore(el.firstChild, el); - } - parent.removeChild(el); - } - - function elementsHaveSameNonClassAttributes(el1, el2) { - if (el1.attributes.length != el2.attributes.length) { - return false; - } - for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) { - attr1 = el1.attributes[i]; - name = attr1.name; - if (name != "class") { - attr2 = el2.attributes.getNamedItem(name); - if (attr1.specified != attr2.specified) { - return false; - } - if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) { - return false; - } - } - } - return true; - } - - function isSplitPoint(node, offset) { - if (rangy.dom.isCharacterDataNode(node)) { - if (offset == 0) { - return !!node.previousSibling; - } else if (offset == node.length) { - return !!node.nextSibling; - } else { - return true; - } - } - - return offset > 0 && offset < node.childNodes.length; - } - - function splitNodeAt(node, descendantNode, descendantOffset) { - var newNode; - if (rangy.dom.isCharacterDataNode(descendantNode)) { - if (descendantOffset == 0) { - descendantOffset = rangy.dom.getNodeIndex(descendantNode); - descendantNode = descendantNode.parentNode; - } else if (descendantOffset == descendantNode.length) { - descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1; - descendantNode = descendantNode.parentNode; - } else { - newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset); - } - } - if (!newNode) { - newNode = descendantNode.cloneNode(false); - if (newNode.id) { - newNode.removeAttribute("id"); - } - var child; - while ((child = descendantNode.childNodes[descendantOffset])) { - newNode.appendChild(child); - } - rangy.dom.insertAfter(newNode, descendantNode); - } - return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode)); - } - - function Merge(firstNode) { - this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE); - this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode; - this.textNodes = [this.firstTextNode]; - } - - Merge.prototype = { - doMerge: function() { - var textBits = [], textNode, parent, text; - for (var i = 0, len = this.textNodes.length; i < len; ++i) { - textNode = this.textNodes[i]; - parent = textNode.parentNode; - textBits[i] = textNode.data; - if (i) { - parent.removeChild(textNode); - if (!parent.hasChildNodes()) { - parent.parentNode.removeChild(parent); - } - } - } - this.firstTextNode.data = text = textBits.join(""); - return text; - }, - - getLength: function() { - var i = this.textNodes.length, len = 0; - while (i--) { - len += this.textNodes[i].length; - } - return len; - }, - - toString: function() { - var textBits = []; - for (var i = 0, len = this.textNodes.length; i < len; ++i) { - textBits[i] = "'" + this.textNodes[i].data + "'"; - } - return "[Merge(" + textBits.join(",") + ")]"; - } - }; - - function HTMLApplier(tagNames, cssClass, similarClassRegExp, normalize) { - this.tagNames = tagNames || [defaultTagName]; - this.cssClass = cssClass || ""; - this.similarClassRegExp = similarClassRegExp; - this.normalize = normalize; - this.applyToAnyTagName = false; - } - - HTMLApplier.prototype = { - getAncestorWithClass: function(node) { - var cssClassMatch; - while (node) { - cssClassMatch = this.cssClass ? hasClass(node, this.cssClass, this.similarClassRegExp) : true; - if (node.nodeType == wysihtml5.ELEMENT_NODE && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) { - return node; - } - node = node.parentNode; - } - return false; - }, - - // Normalizes nodes after applying a CSS class to a Range. - postApply: function(textNodes, range) { - var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1]; - - var merges = [], currentMerge; - - var rangeStartNode = firstNode, rangeEndNode = lastNode; - var rangeStartOffset = 0, rangeEndOffset = lastNode.length; - - var textNode, precedingTextNode; - - for (var i = 0, len = textNodes.length; i < len; ++i) { - textNode = textNodes[i]; - precedingTextNode = this.getAdjacentMergeableTextNode(textNode.parentNode, false); - if (precedingTextNode) { - if (!currentMerge) { - currentMerge = new Merge(precedingTextNode); - merges.push(currentMerge); - } - currentMerge.textNodes.push(textNode); - if (textNode === firstNode) { - rangeStartNode = currentMerge.firstTextNode; - rangeStartOffset = rangeStartNode.length; - } - if (textNode === lastNode) { - rangeEndNode = currentMerge.firstTextNode; - rangeEndOffset = currentMerge.getLength(); - } - } else { - currentMerge = null; - } - } - - // Test whether the first node after the range needs merging - var nextTextNode = this.getAdjacentMergeableTextNode(lastNode.parentNode, true); - if (nextTextNode) { - if (!currentMerge) { - currentMerge = new Merge(lastNode); - merges.push(currentMerge); - } - currentMerge.textNodes.push(nextTextNode); - } - - // Do the merges - if (merges.length) { - for (i = 0, len = merges.length; i < len; ++i) { - merges[i].doMerge(); - } - // Set the range boundaries - range.setStart(rangeStartNode, rangeStartOffset); - range.setEnd(rangeEndNode, rangeEndOffset); - } - }, - - getAdjacentMergeableTextNode: function(node, forward) { - var isTextNode = (node.nodeType == wysihtml5.TEXT_NODE); - var el = isTextNode ? node.parentNode : node; - var adjacentNode; - var propName = forward ? "nextSibling" : "previousSibling"; - if (isTextNode) { - // Can merge if the node's previous/next sibling is a text node - adjacentNode = node[propName]; - if (adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) { - return adjacentNode; - } - } else { - // Compare element with its sibling - adjacentNode = el[propName]; - if (adjacentNode && this.areElementsMergeable(node, adjacentNode)) { - return adjacentNode[forward ? "firstChild" : "lastChild"]; - } - } - return null; - }, - - areElementsMergeable: function(el1, el2) { - return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase()) - && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase()) - && hasSameClasses(el1, el2) - && elementsHaveSameNonClassAttributes(el1, el2); - }, - - createContainer: function(doc) { - var el = doc.createElement(this.tagNames[0]); - if (this.cssClass) { - el.className = this.cssClass; - } - return el; - }, - - applyToTextNode: function(textNode) { - var parent = textNode.parentNode; - if (parent.childNodes.length == 1 && rangy.dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) { - if (this.cssClass) { - addClass(parent, this.cssClass, this.similarClassRegExp); - } - } else { - var el = this.createContainer(rangy.dom.getDocument(textNode)); - textNode.parentNode.insertBefore(el, textNode); - el.appendChild(textNode); - } - }, - - isRemovable: function(el) { - return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) && wysihtml5.lang.string(el.className).trim() == this.cssClass; - }, - - undoToTextNode: function(textNode, range, ancestorWithClass) { - if (!range.containsNode(ancestorWithClass)) { - // Split out the portion of the ancestor from which we can remove the CSS class - var ancestorRange = range.cloneRange(); - ancestorRange.selectNode(ancestorWithClass); - - if (ancestorRange.isPointInRange(range.endContainer, range.endOffset) && isSplitPoint(range.endContainer, range.endOffset)) { - splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset); - range.setEndAfter(ancestorWithClass); - } - if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) { - ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset); - } - } - - if (this.similarClassRegExp) { - removeClass(ancestorWithClass, this.similarClassRegExp); - } - if (this.isRemovable(ancestorWithClass)) { - replaceWithOwnChildren(ancestorWithClass); - } - }, - - applyToRange: function(range) { - var textNodes = range.getNodes([wysihtml5.TEXT_NODE]); - if (!textNodes.length) { - try { - var node = this.createContainer(range.endContainer.ownerDocument); - range.surroundContents(node); - this.selectNode(range, node); - return; - } catch(e) {} - } - - range.splitBoundaries(); - textNodes = range.getNodes([wysihtml5.TEXT_NODE]); - - if (textNodes.length) { - var textNode; - - for (var i = 0, len = textNodes.length; i < len; ++i) { - textNode = textNodes[i]; - if (!this.getAncestorWithClass(textNode)) { - this.applyToTextNode(textNode); - } - } - - range.setStart(textNodes[0], 0); - textNode = textNodes[textNodes.length - 1]; - range.setEnd(textNode, textNode.length); - - if (this.normalize) { - this.postApply(textNodes, range); - } - } - }, - - undoToRange: function(range) { - var textNodes = range.getNodes([wysihtml5.TEXT_NODE]), textNode, ancestorWithClass; - if (textNodes.length) { - range.splitBoundaries(); - textNodes = range.getNodes([wysihtml5.TEXT_NODE]); - } else { - var doc = range.endContainer.ownerDocument, - node = doc.createTextNode(wysihtml5.INVISIBLE_SPACE); - range.insertNode(node); - range.selectNode(node); - textNodes = [node]; - } - - for (var i = 0, len = textNodes.length; i < len; ++i) { - textNode = textNodes[i]; - ancestorWithClass = this.getAncestorWithClass(textNode); - if (ancestorWithClass) { - this.undoToTextNode(textNode, range, ancestorWithClass); - } - } - - if (len == 1) { - this.selectNode(range, textNodes[0]); - } else { - range.setStart(textNodes[0], 0); - textNode = textNodes[textNodes.length - 1]; - range.setEnd(textNode, textNode.length); - - if (this.normalize) { - this.postApply(textNodes, range); - } - } - }, - - selectNode: function(range, node) { - var isElement = node.nodeType === wysihtml5.ELEMENT_NODE, - canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : true, - content = isElement ? node.innerHTML : node.data, - isEmpty = (content === "" || content === wysihtml5.INVISIBLE_SPACE); - - if (isEmpty && isElement && canHaveHTML) { - // Make sure that caret is visible in node by inserting a zero width no breaking space - try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {} - } - range.selectNodeContents(node); - if (isEmpty && isElement) { - range.collapse(false); - } else if (isEmpty) { - range.setStartAfter(node); - range.setEndAfter(node); - } - }, - - getTextSelectedByRange: function(textNode, range) { - var textRange = range.cloneRange(); - textRange.selectNodeContents(textNode); - - var intersectionRange = textRange.intersection(range); - var text = intersectionRange ? intersectionRange.toString() : ""; - textRange.detach(); - - return text; - }, - - isAppliedToRange: function(range) { - var ancestors = [], - ancestor, - textNodes = range.getNodes([wysihtml5.TEXT_NODE]); - if (!textNodes.length) { - ancestor = this.getAncestorWithClass(range.startContainer); - return ancestor ? [ancestor] : false; - } - - for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) { - selectedText = this.getTextSelectedByRange(textNodes[i], range); - ancestor = this.getAncestorWithClass(textNodes[i]); - if (selectedText != "" && !ancestor) { - return false; - } else { - ancestors.push(ancestor); - } - } - return ancestors; - }, - - toggleRange: function(range) { - if (this.isAppliedToRange(range)) { - this.undoToRange(range); - } else { - this.applyToRange(range); - } - } - }; - - wysihtml5.selection.HTMLApplier = HTMLApplier; - -})(wysihtml5, rangy);/** - * Rich Text Query/Formatting Commands - * - * @example - * var commands = new wysihtml5.Commands(editor); - */ -wysihtml5.Commands = Base.extend( - /** @scope wysihtml5.Commands.prototype */ { - constructor: function(editor) { - this.editor = editor; - this.composer = editor.composer; - this.doc = this.composer.doc; - }, - - /** - * Check whether the browser supports the given command - * - * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList") - * @example - * commands.supports("createLink"); - */ - support: function(command) { - return wysihtml5.browser.supportsCommand(this.doc, command); - }, - - /** - * Check whether the browser supports the given command - * - * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList") - * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...) - * @example - * commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg"); - */ - exec: function(command, value) { - var obj = wysihtml5.commands[command], - args = wysihtml5.lang.array(arguments).get(), - method = obj && obj.exec, - result = null; - - this.editor.fire("beforecommand:composer"); - - if (method) { - args.unshift(this.composer); - result = method.apply(obj, args); - } else { - try { - // try/catch for buggy firefox - result = this.doc.execCommand(command, false, value); - } catch(e) {} - } - - this.editor.fire("aftercommand:composer"); - return result; - }, - - /** - * Check whether the current command is active - * If the caret is within a bold text, then calling this with command "bold" should return true - * - * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList") - * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src) - * @return {Boolean} Whether the command is active - * @example - * var isCurrentSelectionBold = commands.state("bold"); - */ - state: function(command, commandValue) { - var obj = wysihtml5.commands[command], - args = wysihtml5.lang.array(arguments).get(), - method = obj && obj.state; - if (method) { - args.unshift(this.composer); - return method.apply(obj, args); - } else { - try { - // try/catch for buggy firefox - return this.doc.queryCommandState(command); - } catch(e) { - return false; - } - } - }, - - /** - * Get the current command's value - * - * @param {String} command The command string which to check (eg. "formatBlock") - * @return {String} The command value - * @example - * var currentBlockElement = commands.value("formatBlock"); - */ - value: function(command) { - var obj = wysihtml5.commands[command], - method = obj && obj.value; - if (method) { - return method.call(obj, this.composer, command); - } else { - try { - // try/catch for buggy firefox - return this.doc.queryCommandValue(command); - } catch(e) { - return null; - } - } - } -}); -(function(wysihtml5) { - var undef; - - wysihtml5.commands.bold = { - exec: function(composer, command) { - return wysihtml5.commands.formatInline.exec(composer, command, "b"); - }, - - state: function(composer, command, color) { - // element.ownerDocument.queryCommandState("bold") results: - // firefox: only <b> - // chrome: <b>, <strong>, <h1>, <h2>, ... - // ie: <b>, <strong> - // opera: <b>, <strong> - return wysihtml5.commands.formatInline.state(composer, command, "b"); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5); - -(function(wysihtml5) { - var undef, - NODE_NAME = "A", - dom = wysihtml5.dom; - - function _removeFormat(composer, anchors) { - var length = anchors.length, - i = 0, - anchor, - codeElement, - textContent; - for (; i<length; i++) { - anchor = anchors[i]; - codeElement = dom.getParentElement(anchor, { nodeName: "code" }); - textContent = dom.getTextContent(anchor); - - // if <a> contains url-like text content, rename it to <code> to prevent re-autolinking - // else replace <a> with its childNodes - if (textContent.match(dom.autoLink.URL_REG_EXP) && !codeElement) { - // <code> element is used to prevent later auto-linking of the content - codeElement = dom.renameElement(anchor, "code"); - } else { - dom.replaceWithChildNodes(anchor); - } - } - } - - function _format(composer, attributes) { - var doc = composer.doc, - tempClass = "_wysihtml5-temp-" + (+new Date()), - tempClassRegExp = /non-matching-class/g, - i = 0, - length, - anchors, - anchor, - hasElementChild, - isEmpty, - elementToSetCaretAfter, - textContent, - whiteSpace, - j; - wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp); - anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass); - length = anchors.length; - for (; i<length; i++) { - anchor = anchors[i]; - anchor.removeAttribute("class"); - for (j in attributes) { - anchor.setAttribute(j, attributes[j]); - } - } - - elementToSetCaretAfter = anchor; - if (length === 1) { - textContent = dom.getTextContent(anchor); - hasElementChild = !!anchor.querySelector("*"); - isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE; - if (!hasElementChild && isEmpty) { - dom.setTextContent(anchor, attributes.text || anchor.href); - whiteSpace = doc.createTextNode(" "); - composer.selection.setAfter(anchor); - composer.selection.insertNode(whiteSpace); - elementToSetCaretAfter = whiteSpace; - } - } - composer.selection.setAfter(elementToSetCaretAfter); - } - - wysihtml5.commands.createLink = { - /** - * TODO: Use HTMLApplier or formatInline here - * - * Turns selection into a link - * If selection is already a link, it removes the link and wraps it with a <code> element - * The <code> element is needed to avoid auto linking - * - * @example - * // either ... - * wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de"); - * // ... or ... - * wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" }); - */ - exec: function(composer, command, value) { - var anchors = this.state(composer, command); - if (anchors) { - // Selection contains links - composer.selection.executeAndRestore(function() { - _removeFormat(composer, anchors); - }); - } else { - // Create links - value = typeof(value) === "object" ? value : { href: value }; - _format(composer, value); - } - }, - - state: function(composer, command) { - return wysihtml5.commands.formatInline.state(composer, command, "A"); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);/** - * document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags - * which we don't want - * Instead we set a css class - */ -(function(wysihtml5) { - var undef, - REG_EXP = /wysiwyg-font-size-[a-z\-]+/g; - - wysihtml5.commands.fontSize = { - exec: function(composer, command, size) { - return wysihtml5.commands.formatInline.exec(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP); - }, - - state: function(composer, command, size) { - return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5); -/** - * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags - * which we don't want - * Instead we set a css class - */ -(function(wysihtml5) { - var undef, - REG_EXP = /wysiwyg-color-[a-z]+/g; - - wysihtml5.commands.foreColor = { - exec: function(composer, command, color) { - return wysihtml5.commands.formatInline.exec(composer, command, "span", "wysiwyg-color-" + color, REG_EXP); - }, - - state: function(composer, command, color) { - return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef, - dom = wysihtml5.dom, - DEFAULT_NODE_NAME = "DIV", - // Following elements are grouped - // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4 - // instead of creating a H4 within a H1 which would result in semantically invalid html - BLOCK_ELEMENTS_GROUP = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", DEFAULT_NODE_NAME]; - - /** - * Remove similiar classes (based on classRegExp) - * and add the desired class name - */ - function _addClass(element, className, classRegExp) { - if (element.className) { - _removeClass(element, classRegExp); - element.className += " " + className; - } else { - element.className = className; - } - } - - function _removeClass(element, classRegExp) { - element.className = element.className.replace(classRegExp, ""); - } - - /** - * Check whether given node is a text node and whether it's empty - */ - function _isBlankTextNode(node) { - return node.nodeType === wysihtml5.TEXT_NODE && !wysihtml5.lang.string(node.data).trim(); - } - - /** - * Returns previous sibling node that is not a blank text node - */ - function _getPreviousSiblingThatIsNotBlank(node) { - var previousSibling = node.previousSibling; - while (previousSibling && _isBlankTextNode(previousSibling)) { - previousSibling = previousSibling.previousSibling; - } - return previousSibling; - } - - /** - * Returns next sibling node that is not a blank text node - */ - function _getNextSiblingThatIsNotBlank(node) { - var nextSibling = node.nextSibling; - while (nextSibling && _isBlankTextNode(nextSibling)) { - nextSibling = nextSibling.nextSibling; - } - return nextSibling; - } - - /** - * Adds line breaks before and after the given node if the previous and next siblings - * aren't already causing a visual line break (block element or <br>) - */ - function _addLineBreakBeforeAndAfter(node) { - var doc = node.ownerDocument, - nextSibling = _getNextSiblingThatIsNotBlank(node), - previousSibling = _getPreviousSiblingThatIsNotBlank(node); - - if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) { - node.parentNode.insertBefore(doc.createElement("br"), nextSibling); - } - if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) { - node.parentNode.insertBefore(doc.createElement("br"), node); - } - } - - /** - * Removes line breaks before and after the given node - */ - function _removeLineBreakBeforeAndAfter(node) { - var nextSibling = _getNextSiblingThatIsNotBlank(node), - previousSibling = _getPreviousSiblingThatIsNotBlank(node); - - if (nextSibling && _isLineBreak(nextSibling)) { - nextSibling.parentNode.removeChild(nextSibling); - } - if (previousSibling && _isLineBreak(previousSibling)) { - previousSibling.parentNode.removeChild(previousSibling); - } - } - - function _removeLastChildIfLineBreak(node) { - var lastChild = node.lastChild; - if (lastChild && _isLineBreak(lastChild)) { - lastChild.parentNode.removeChild(lastChild); - } - } - - function _isLineBreak(node) { - return node.nodeName === "BR"; - } - - /** - * Checks whether the elment causes a visual line break - * (<br> or block elements) - */ - function _isLineBreakOrBlockElement(element) { - if (_isLineBreak(element)) { - return true; - } - - if (dom.getStyle("display").from(element) === "block") { - return true; - } - - return false; - } - - /** - * Execute native query command - * and if necessary modify the inserted node's className - */ - function _execCommand(doc, command, nodeName, className) { - if (className) { - var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) { - var target = event.target, - displayStyle; - if (target.nodeType !== wysihtml5.ELEMENT_NODE) { - return; - } - displayStyle = dom.getStyle("display").from(target); - if (displayStyle.substr(0, 6) !== "inline") { - // Make sure that only block elements receive the given class - target.className += " " + className; - } - }); - } - doc.execCommand(command, false, nodeName); - if (eventListener) { - eventListener.stop(); - } - } - - function _selectLineAndWrap(composer, element) { - composer.selection.selectLine(); - composer.selection.surround(element); - _removeLineBreakBeforeAndAfter(element); - _removeLastChildIfLineBreak(element); - composer.selection.selectNode(element); - } - - function _hasClasses(element) { - return !!wysihtml5.lang.string(element.className).trim(); - } - - wysihtml5.commands.formatBlock = { - exec: function(composer, command, nodeName, className, classRegExp) { - var doc = composer.doc, - blockElement = this.state(composer, command, nodeName, className, classRegExp), - selectedNode; - - nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName; - - if (blockElement) { - composer.selection.executeAndRestoreSimple(function() { - if (classRegExp) { - _removeClass(blockElement, classRegExp); - } - var hasClasses = _hasClasses(blockElement); - if (!hasClasses && blockElement.nodeName === (nodeName || DEFAULT_NODE_NAME)) { - // Insert a line break afterwards and beforewards when there are siblings - // that are not of type line break or block element - _addLineBreakBeforeAndAfter(blockElement); - dom.replaceWithChildNodes(blockElement); - } else if (hasClasses) { - // Make sure that styling is kept by renaming the element to <div> and copying over the class name - dom.renameElement(blockElement, DEFAULT_NODE_NAME); - } - }); - return; - } - - // Find similiar block element and rename it (<h2 class="foo"></h2> => <h1 class="foo"></h1>) - if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) { - selectedNode = composer.selection.getSelectedNode(); - blockElement = dom.getParentElement(selectedNode, { - nodeName: BLOCK_ELEMENTS_GROUP - }); - - if (blockElement) { - composer.selection.executeAndRestoreSimple(function() { - // Rename current block element to new block element and add class - if (nodeName) { - blockElement = dom.renameElement(blockElement, nodeName); - } - if (className) { - _addClass(blockElement, className, classRegExp); - } - }); - return; - } - } - - if (composer.commands.support(command)) { - _execCommand(doc, command, nodeName || DEFAULT_NODE_NAME, className); - return; - } - - blockElement = doc.createElement(nodeName || DEFAULT_NODE_NAME); - if (className) { - blockElement.className = className; - } - _selectLineAndWrap(composer, blockElement); - }, - - state: function(composer, command, nodeName, className, classRegExp) { - nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName; - var selectedNode = composer.selection.getSelectedNode(); - return dom.getParentElement(selectedNode, { - nodeName: nodeName, - className: className, - classRegExp: classRegExp - }); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);/** - * formatInline scenarios for tag "B" (| = caret, |foo| = selected text) - * - * #1 caret in unformatted text: - * abcdefg| - * output: - * abcdefg<b>|</b> - * - * #2 unformatted text selected: - * abc|deg|h - * output: - * abc<b>|deg|</b>h - * - * #3 unformatted text selected across boundaries: - * ab|c <span>defg|h</span> - * output: - * ab<b>|c </b><span><b>defg</b>|h</span> - * - * #4 formatted text entirely selected - * <b>|abc|</b> - * output: - * |abc| - * - * #5 formatted text partially selected - * <b>ab|c|</b> - * output: - * <b>ab</b>|c| - * - * #6 formatted text selected across boundaries - * <span>ab|c</span> <b>de|fgh</b> - * output: - * <span>ab|c</span> de|<b>fgh</b> - */ -(function(wysihtml5) { - var undef, - // Treat <b> as <strong> and vice versa - ALIAS_MAPPING = { - "strong": "b", - "em": "i", - "b": "strong", - "i": "em" - }, - htmlApplier = {}; - - function _getTagNames(tagName) { - var alias = ALIAS_MAPPING[tagName]; - return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()]; - } - - function _getApplier(tagName, className, classRegExp) { - var identifier = tagName + ":" + className; - if (!htmlApplier[identifier]) { - htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true); - } - return htmlApplier[identifier]; - } - - wysihtml5.commands.formatInline = { - exec: function(composer, command, tagName, className, classRegExp) { - var range = composer.selection.getRange(); - if (!range) { - return false; - } - _getApplier(tagName, className, classRegExp).toggleRange(range); - composer.selection.setSelection(range); - }, - - state: function(composer, command, tagName, className, classRegExp) { - var doc = composer.doc, - aliasTagName = ALIAS_MAPPING[tagName] || tagName, - range; - - // Check whether the document contains a node with the desired tagName - if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) && - !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) { - return false; - } - - // Check whether the document contains a node with the desired className - if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) { - return false; - } - - range = composer.selection.getRange(); - if (!range) { - return false; - } - - return _getApplier(tagName, className, classRegExp).isAppliedToRange(range); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef; - - wysihtml5.commands.insertHTML = { - exec: function(composer, command, html) { - if (composer.commands.support(command)) { - composer.doc.execCommand(command, false, html); - } else { - composer.selection.insertHTML(html); - } - }, - - state: function() { - return false; - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var NODE_NAME = "IMG"; - - wysihtml5.commands.insertImage = { - /** - * Inserts an <img> - * If selection is already an image link, it removes it - * - * @example - * // either ... - * wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg"); - * // ... or ... - * wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" }); - */ - exec: function(composer, command, value) { - value = typeof(value) === "object" ? value : { src: value }; - - var doc = composer.doc, - image = this.state(composer), - textNode, - i, - parent; - - if (image) { - // Image already selected, set the caret before it and delete it - composer.selection.setBefore(image); - parent = image.parentNode; - parent.removeChild(image); - - // and it's parent <a> too if it hasn't got any other relevant child nodes - wysihtml5.dom.removeEmptyTextNodes(parent); - if (parent.nodeName === "A" && !parent.firstChild) { - composer.selection.setAfter(parent); - parent.parentNode.removeChild(parent); - } - - // firefox and ie sometimes don't remove the image handles, even though the image got removed - wysihtml5.quirks.redraw(composer.element); - return; - } - - image = doc.createElement(NODE_NAME); - - for (i in value) { - image[i] = value[i]; - } - - composer.selection.insertNode(image); - if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) { - textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE); - composer.selection.insertNode(textNode); - composer.selection.setAfter(textNode); - } else { - composer.selection.setAfter(image); - } - }, - - state: function(composer) { - var doc = composer.doc, - selectedNode, - text, - imagesInSelection; - - if (!wysihtml5.dom.hasElementWithTagName(doc, NODE_NAME)) { - return false; - } - - selectedNode = composer.selection.getSelectedNode(); - if (!selectedNode) { - return false; - } - - if (selectedNode.nodeName === NODE_NAME) { - // This works perfectly in IE - return selectedNode; - } - - if (selectedNode.nodeType !== wysihtml5.ELEMENT_NODE) { - return false; - } - - text = composer.selection.getText(); - text = wysihtml5.lang.string(text).trim(); - if (text) { - return false; - } - - imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) { - return node.nodeName === "IMG"; - }); - - if (imagesInSelection.length !== 1) { - return false; - } - - return imagesInSelection[0]; - }, - - value: function(composer) { - var image = this.state(composer); - return image && image.src; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef, - LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : ""); - - wysihtml5.commands.insertLineBreak = { - exec: function(composer, command) { - if (composer.commands.support(command)) { - composer.doc.execCommand(command, false, null); - if (!wysihtml5.browser.autoScrollsToCaret()) { - composer.selection.scrollIntoView(); - } - } else { - composer.commands.exec("insertHTML", LINE_BREAK); - } - }, - - state: function() { - return false; - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef; - - wysihtml5.commands.insertOrderedList = { - exec: function(composer, command) { - var doc = composer.doc, - selectedNode = composer.selection.getSelectedNode(), - list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }), - otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }), - tempClassName = "_wysihtml5-temp-" + new Date().getTime(), - isEmpty, - tempElement; - - if (composer.commands.support(command)) { - doc.execCommand(command, false, null); - return; - } - - if (list) { - // Unwrap list - // <ol><li>foo</li><li>bar</li></ol> - // becomes: - // foo<br>bar<br> - composer.selection.executeAndRestoreSimple(function() { - wysihtml5.dom.resolveList(list); - }); - } else if (otherList) { - // Turn an unordered list into an ordered list - // <ul><li>foo</li><li>bar</li></ul> - // becomes: - // <ol><li>foo</li><li>bar</li></ol> - composer.selection.executeAndRestoreSimple(function() { - wysihtml5.dom.renameElement(otherList, "ol"); - }); - } else { - // Create list - composer.commands.exec("formatBlock", "div", tempClassName); - tempElement = doc.querySelector("." + tempClassName); - isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE; - composer.selection.executeAndRestoreSimple(function() { - list = wysihtml5.dom.convertToList(tempElement, "ol"); - }); - if (isEmpty) { - composer.selection.selectNode(list.querySelector("li")); - } - } - }, - - state: function(composer) { - var selectedNode = composer.selection.getSelectedNode(); - return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef; - - wysihtml5.commands.insertUnorderedList = { - exec: function(composer, command) { - var doc = composer.doc, - selectedNode = composer.selection.getSelectedNode(), - list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }), - otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }), - tempClassName = "_wysihtml5-temp-" + new Date().getTime(), - isEmpty, - tempElement; - - if (composer.commands.support(command)) { - doc.execCommand(command, false, null); - return; - } - - if (list) { - // Unwrap list - // <ul><li>foo</li><li>bar</li></ul> - // becomes: - // foo<br>bar<br> - composer.selection.executeAndRestoreSimple(function() { - wysihtml5.dom.resolveList(list); - }); - } else if (otherList) { - // Turn an ordered list into an unordered list - // <ol><li>foo</li><li>bar</li></ol> - // becomes: - // <ul><li>foo</li><li>bar</li></ul> - composer.selection.executeAndRestoreSimple(function() { - wysihtml5.dom.renameElement(otherList, "ul"); - }); - } else { - // Create list - composer.commands.exec("formatBlock", "div", tempClassName); - tempElement = doc.querySelector("." + tempClassName); - isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE; - composer.selection.executeAndRestoreSimple(function() { - list = wysihtml5.dom.convertToList(tempElement, "ul"); - }); - if (isEmpty) { - composer.selection.selectNode(list.querySelector("li")); - } - } - }, - - state: function(composer) { - var selectedNode = composer.selection.getSelectedNode(); - return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef; - - wysihtml5.commands.italic = { - exec: function(composer, command) { - return wysihtml5.commands.formatInline.exec(composer, command, "i"); - }, - - state: function(composer, command, color) { - // element.ownerDocument.queryCommandState("italic") results: - // firefox: only <i> - // chrome: <i>, <em>, <blockquote>, ... - // ie: <i>, <em> - // opera: only <i> - return wysihtml5.commands.formatInline.state(composer, command, "i"); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef, - CLASS_NAME = "wysiwyg-text-align-center", - REG_EXP = /wysiwyg-text-align-[a-z]+/g; - - wysihtml5.commands.justifyCenter = { - exec: function(composer, command) { - return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP); - }, - - state: function(composer, command) { - return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef, - CLASS_NAME = "wysiwyg-text-align-left", - REG_EXP = /wysiwyg-text-align-[a-z]+/g; - - wysihtml5.commands.justifyLeft = { - exec: function(composer, command) { - return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP); - }, - - state: function(composer, command) { - return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef, - CLASS_NAME = "wysiwyg-text-align-right", - REG_EXP = /wysiwyg-text-align-[a-z]+/g; - - wysihtml5.commands.justifyRight = { - exec: function(composer, command) { - return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP); - }, - - state: function(composer, command) { - return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);(function(wysihtml5) { - var undef; - wysihtml5.commands.underline = { - exec: function(composer, command) { - return wysihtml5.commands.formatInline.exec(composer, command, "u"); - }, - - state: function(composer, command) { - return wysihtml5.commands.formatInline.state(composer, command, "u"); - }, - - value: function() { - return undef; - } - }; -})(wysihtml5);/** - * Undo Manager for wysihtml5 - * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface - */ -(function(wysihtml5) { - var Z_KEY = 90, - Y_KEY = 89, - BACKSPACE_KEY = 8, - DELETE_KEY = 46, - MAX_HISTORY_ENTRIES = 40, - UNDO_HTML = '<span id="_wysihtml5-undo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>', - REDO_HTML = '<span id="_wysihtml5-redo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>', - dom = wysihtml5.dom; - - function cleanTempElements(doc) { - var tempElement; - while (tempElement = doc.querySelector("._wysihtml5-temp")) { - tempElement.parentNode.removeChild(tempElement); - } - } - - wysihtml5.UndoManager = wysihtml5.lang.Dispatcher.extend( - /** @scope wysihtml5.UndoManager.prototype */ { - constructor: function(editor) { - this.editor = editor; - this.composer = editor.composer; - this.element = this.composer.element; - this.history = [this.composer.getValue()]; - this.position = 1; - - // Undo manager currently only supported in browsers who have the insertHTML command (not IE) - if (this.composer.commands.support("insertHTML")) { - this._observe(); - } - }, - - _observe: function() { - var that = this, - doc = this.composer.sandbox.getDocument(), - lastKey; - - // Catch CTRL+Z and CTRL+Y - dom.observe(this.element, "keydown", function(event) { - if (event.altKey || (!event.ctrlKey && !event.metaKey)) { - return; - } - - var keyCode = event.keyCode, - isUndo = keyCode === Z_KEY && !event.shiftKey, - isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY); - - if (isUndo) { - that.undo(); - event.preventDefault(); - } else if (isRedo) { - that.redo(); - event.preventDefault(); - } - }); - - // Catch delete and backspace - dom.observe(this.element, "keydown", function(event) { - var keyCode = event.keyCode; - if (keyCode === lastKey) { - return; - } - - lastKey = keyCode; - - if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) { - that.transact(); - } - }); - - // Now this is very hacky: - // These days browsers don't offer a undo/redo event which we could hook into - // to be notified when the user hits undo/redo in the contextmenu. - // Therefore we simply insert two elements as soon as the contextmenu gets opened. - // The last element being inserted will be immediately be removed again by a exexCommand("undo") - // => When the second element appears in the dom tree then we know the user clicked "redo" in the context menu - // => When the first element disappears from the dom tree then we know the user clicked "undo" in the context menu - if (wysihtml5.browser.hasUndoInContextMenu()) { - var interval, observed, cleanUp = function() { - cleanTempElements(doc); - clearInterval(interval); - }; - - dom.observe(this.element, "contextmenu", function() { - cleanUp(); - that.composer.selection.executeAndRestoreSimple(function() { - if (that.element.lastChild) { - that.composer.selection.setAfter(that.element.lastChild); - } - - // enable undo button in context menu - doc.execCommand("insertHTML", false, UNDO_HTML); - // enable redo button in context menu - doc.execCommand("insertHTML", false, REDO_HTML); - doc.execCommand("undo", false, null); - }); - - interval = setInterval(function() { - if (doc.getElementById("_wysihtml5-redo")) { - cleanUp(); - that.redo(); - } else if (!doc.getElementById("_wysihtml5-undo")) { - cleanUp(); - that.undo(); - } - }, 400); - - if (!observed) { - observed = true; - dom.observe(document, "mousedown", cleanUp); - dom.observe(doc, ["mousedown", "paste", "cut", "copy"], cleanUp); - } - }); - } - - this.editor - .observe("newword:composer", function() { - that.transact(); - }) - - .observe("beforecommand:composer", function() { - that.transact(); - }); - }, - - transact: function() { - var previousHtml = this.history[this.position - 1], - currentHtml = this.composer.getValue(); - - if (currentHtml == previousHtml) { - return; - } - - var length = this.history.length = this.position; - if (length > MAX_HISTORY_ENTRIES) { - this.history.shift(); - this.position--; - } - - this.position++; - this.history.push(currentHtml); - }, - - undo: function() { - this.transact(); - - if (this.position <= 1) { - return; - } - - this.set(this.history[--this.position - 1]); - this.editor.fire("undo:composer"); - }, - - redo: function() { - if (this.position >= this.history.length) { - return; - } - - this.set(this.history[++this.position - 1]); - this.editor.fire("redo:composer"); - }, - - set: function(html) { - this.composer.setValue(html); - this.editor.focus(true); - } - }); -})(wysihtml5); -/** - * TODO: the following methods still need unit test coverage - */ -wysihtml5.views.View = Base.extend( - /** @scope wysihtml5.views.View.prototype */ { - constructor: function(parent, textareaElement, config) { - this.parent = parent; - this.element = textareaElement; - this.config = config; - - this._observeViewChange(); - }, - - _observeViewChange: function() { - var that = this; - this.parent.observe("beforeload", function() { - that.parent.observe("change_view", function(view) { - if (view === that.name) { - that.parent.currentView = that; - that.show(); - // Using tiny delay here to make sure that the placeholder is set before focusing - setTimeout(function() { that.focus(); }, 0); - } else { - that.hide(); - } - }); - }); - }, - - focus: function() { - if (this.element.ownerDocument.querySelector(":focus") === this.element) { - return; - } - - try { this.element.focus(); } catch(e) {} - }, - - hide: function() { - this.element.style.display = "none"; - }, - - show: function() { - this.element.style.display = ""; - }, - - disable: function() { - this.element.setAttribute("disabled", "disabled"); - }, - - enable: function() { - this.element.removeAttribute("disabled"); - } -});(function(wysihtml5) { - var dom = wysihtml5.dom, - browser = wysihtml5.browser; - - wysihtml5.views.Composer = wysihtml5.views.View.extend( - /** @scope wysihtml5.views.Composer.prototype */ { - name: "composer", - - // Needed for firefox in order to display a proper caret in an empty contentEditable - CARET_HACK: "<br>", - - constructor: function(parent, textareaElement, config) { - this.base(parent, textareaElement, config); - this.textarea = this.parent.textarea; - this._initSandbox(); - }, - - clear: function() { - this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK; - }, - - getValue: function(parse) { - var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element); - - if (parse) { - value = this.parent.parse(value); - } - - // Replace all "zero width no breaking space" chars - // which are used as hacks to enable some functionalities - // Also remove all CARET hacks that somehow got left - value = wysihtml5.lang.string(value).replace(wysihtml5.INVISIBLE_SPACE).by(""); - - return value; - }, - - setValue: function(html, parse) { - if (parse) { - html = this.parent.parse(html); - } - this.element.innerHTML = html; - }, - - show: function() { - this.iframe.style.display = this._displayStyle || ""; - - // Firefox needs this, otherwise contentEditable becomes uneditable - this.disable(); - this.enable(); - }, - - hide: function() { - this._displayStyle = dom.getStyle("display").from(this.iframe); - if (this._displayStyle === "none") { - this._displayStyle = null; - } - this.iframe.style.display = "none"; - }, - - disable: function() { - this.element.removeAttribute("contentEditable"); - this.base(); - }, - - enable: function() { - this.element.setAttribute("contentEditable", "true"); - this.base(); - }, - - focus: function(setToEnd) { - // IE 8 fires the focus event after .focus() - // This is needed by our simulate_placeholder.js to work - // therefore we clear it ourselves this time - if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) { - this.clear(); - } - - this.base(); - - var lastChild = this.element.lastChild; - if (setToEnd && lastChild) { - if (lastChild.nodeName === "BR") { - this.selection.setBefore(this.element.lastChild); - } else { - this.selection.setAfter(this.element.lastChild); - } - } - }, - - getTextContent: function() { - return dom.getTextContent(this.element); - }, - - hasPlaceholderSet: function() { - return this.getTextContent() == this.textarea.element.getAttribute("placeholder"); - }, - - isEmpty: function() { - var innerHTML = this.element.innerHTML, - elementsWithVisualValue = "blockquote, ul, ol, img, embed, object, table, iframe, svg, video, audio, button, input, select, textarea"; - return innerHTML === "" || - innerHTML === this.CARET_HACK || - this.hasPlaceholderSet() || - (this.getTextContent() === "" && !this.element.querySelector(elementsWithVisualValue)); - }, - - _initSandbox: function() { - var that = this; - - this.sandbox = new dom.Sandbox(function() { - that._create(); - }, { - stylesheets: this.config.stylesheets - }); - this.iframe = this.sandbox.getIframe(); - - // Create hidden field which tells the server after submit, that the user used an wysiwyg editor - var hiddenField = document.createElement("input"); - hiddenField.type = "hidden"; - hiddenField.name = "_wysihtml5_mode"; - hiddenField.value = 1; - - // Store reference to current wysihtml5 instance on the textarea element - var textareaElement = this.textarea.element; - dom.insert(this.iframe).after(textareaElement); - dom.insert(hiddenField).after(textareaElement); - }, - - _create: function() { - var that = this; - - this.doc = this.sandbox.getDocument(); - this.element = this.doc.body; - this.textarea = this.parent.textarea; - this.element.innerHTML = this.textarea.getValue(true); - this.enable(); - - // Make sure our selection handler is ready - this.selection = new wysihtml5.Selection(this.parent); - - // Make sure commands dispatcher is ready - this.commands = new wysihtml5.Commands(this.parent); - - dom.copyAttributes([ - "className", "spellcheck", "title", "lang", "dir", "accessKey" - ]).from(this.textarea.element).to(this.element); - - dom.addClass(this.element, this.config.composerClassName); - - // Make the editor look like the original textarea, by syncing styles - if (this.config.style) { - this.style(); - } - - this.observe(); - - var name = this.config.name; - if (name) { - dom.addClass(this.element, name); - dom.addClass(this.iframe, name); - } - - // Simulate html5 placeholder attribute on contentEditable element - var placeholderText = typeof(this.config.placeholder) === "string" - ? this.config.placeholder - : this.textarea.element.getAttribute("placeholder"); - if (placeholderText) { - dom.simulatePlaceholder(this.parent, this, placeholderText); - } - - // Make sure that the browser avoids using inline styles whenever possible - this.commands.exec("styleWithCSS", false); - - this._initAutoLinking(); - this._initObjectResizing(); - this._initUndoManager(); - - // Simulate html5 autofocus on contentEditable element - if (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) { - setTimeout(function() { that.focus(); }, 100); - } - - wysihtml5.quirks.insertLineBreakOnReturn(this); - - // IE sometimes leaves a single paragraph, which can't be removed by the user - if (!browser.clearsContentEditableCorrectly()) { - wysihtml5.quirks.ensureProperClearing(this); - } - - if (!browser.clearsListsInContentEditableCorrectly()) { - wysihtml5.quirks.ensureProperClearingOfLists(this); - } - - // Set up a sync that makes sure that textarea and editor have the same content - if (this.initSync && this.config.sync) { - this.initSync(); - } - - // Okay hide the textarea, we are ready to go - this.textarea.hide(); - - // Fire global (before-)load event - this.parent.fire("beforeload").fire("load"); - }, - - _initAutoLinking: function() { - var that = this, - supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(), - supportsAutoLinking = browser.doesAutoLinkingInContentEditable(); - if (supportsDisablingOfAutoLinking) { - this.commands.exec("autoUrlDetect", false); - } - - if (!this.config.autoLink) { - return; - } - - // Only do the auto linking by ourselves when the browser doesn't support auto linking - // OR when he supports auto linking but we were able to turn it off (IE9+) - if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) { - this.parent.observe("newword:composer", function() { - that.selection.executeAndRestore(function(startContainer, endContainer) { - dom.autoLink(endContainer.parentNode); - }); - }); - } - - // Assuming we have the following: - // <a href="http://www.google.de">http://www.google.de</a> - // If a user now changes the url in the innerHTML we want to make sure that - // it's synchronized with the href attribute (as long as the innerHTML is still a url) - var // Use a live NodeList to check whether there are any links in the document - links = this.sandbox.getDocument().getElementsByTagName("a"), - // The autoLink helper method reveals a reg exp to detect correct urls - urlRegExp = dom.autoLink.URL_REG_EXP, - getTextContent = function(element) { - var textContent = wysihtml5.lang.string(dom.getTextContent(element)).trim(); - if (textContent.substr(0, 4) === "www.") { - textContent = "http://" + textContent; - } - return textContent; - }; - - dom.observe(this.element, "keydown", function(event) { - if (!links.length) { - return; - } - - var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument), - link = dom.getParentElement(selectedNode, { nodeName: "A" }, 4), - textContent; - - if (!link) { - return; - } - - textContent = getTextContent(link); - // keydown is fired before the actual content is changed - // therefore we set a timeout to change the href - setTimeout(function() { - var newTextContent = getTextContent(link); - if (newTextContent === textContent) { - return; - } - - // Only set href when new href looks like a valid url - if (newTextContent.match(urlRegExp)) { - link.setAttribute("href", newTextContent); - } - }, 0); - }); - }, - - _initObjectResizing: function() { - var properties = ["width", "height"], - propertiesLength = properties.length, - element = this.element; - - this.commands.exec("enableObjectResizing", this.config.allowObjectResizing); - - if (this.config.allowObjectResizing) { - // IE sets inline styles after resizing objects - // The following lines make sure that the width/height css properties - // are copied over to the width/height attributes - if (browser.supportsEvent("resizeend")) { - dom.observe(element, "resizeend", function(event) { - var target = event.target || event.srcElement, - style = target.style, - i = 0, - property; - for(; i<propertiesLength; i++) { - property = properties[i]; - if (style[property]) { - target.setAttribute(property, parseInt(style[property], 10)); - style[property] = ""; - } - } - // After resizing IE sometimes forgets to remove the old resize handles - wysihtml5.quirks.redraw(element); - }); - } - } else { - if (browser.supportsEvent("resizestart")) { - dom.observe(element, "resizestart", function(event) { event.preventDefault(); }); - } - } - }, - - _initUndoManager: function() { - new wysihtml5.UndoManager(this.parent); - } - }); -})(wysihtml5);(function(wysihtml5) { - var dom = wysihtml5.dom, - doc = document, - win = window, - HOST_TEMPLATE = doc.createElement("div"), - /** - * Styles to copy from textarea to the composer element - */ - TEXT_FORMATTING = [ - "background-color", - "color", "cursor", - "font-family", "font-size", "font-style", "font-variant", "font-weight", - "line-height", "letter-spacing", - "text-align", "text-decoration", "text-indent", "text-rendering", - "word-break", "word-wrap", "word-spacing" - ], - /** - * Styles to copy from textarea to the iframe - */ - BOX_FORMATTING = [ - "background-color", - "border-collapse", - "border-bottom-color", "border-bottom-style", "border-bottom-width", - "border-left-color", "border-left-style", "border-left-width", - "border-right-color", "border-right-style", "border-right-width", - "border-top-color", "border-top-style", "border-top-width", - "clear", "display", "float", - "margin-bottom", "margin-left", "margin-right", "margin-top", - "outline-color", "outline-offset", "outline-width", "outline-style", - "padding-left", "padding-right", "padding-top", "padding-bottom", - "position", "top", "left", "right", "bottom", "z-index", - "vertical-align", "text-align", - "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing", - "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow", - "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius", - "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius", - "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius", - "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius", - "width", "height" - ], - /** - * Styles to sync while the window gets resized - */ - RESIZE_STYLE = [ - "width", "height", - "top", "left", "right", "bottom" - ], - ADDITIONAL_CSS_RULES = [ - "html { height: 100%; }", - "body { min-height: 100%; padding: 0; margin: 0; margin-top: -1px; padding-top: 1px; }", - "._wysihtml5-temp { display: none; }", - wysihtml5.browser.isGecko ? - "body.placeholder { color: graytext !important; }" : - "body.placeholder { color: #a9a9a9 !important; }", - "body[disabled] { background-color: #eee !important; color: #999 !important; cursor: default !important; }", - // Ensure that user see's broken images and can delete them - "img:-moz-broken { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }" - ]; - - /** - * With "setActive" IE offers a smart way of focusing elements without scrolling them into view: - * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx - * - * Other browsers need a more hacky way: (pssst don't tell my mama) - * In order to prevent the element being scrolled into view when focusing it, we simply - * move it out of the scrollable area, focus it, and reset it's position - */ - var focusWithoutScrolling = function(element) { - if (element.setActive) { - // Following line could cause a js error when the textarea is invisible - // See https://github.com/xing/wysihtml5/issues/9 - try { element.setActive(); } catch(e) {} - } else { - var elementStyle = element.style, - originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, - originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft, - originalStyles = { - position: elementStyle.position, - top: elementStyle.top, - left: elementStyle.left, - WebkitUserSelect: elementStyle.WebkitUserSelect - }; - - dom.setStyles({ - position: "absolute", - top: "-99999px", - left: "-99999px", - // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother - WebkitUserSelect: "none" - }).on(element); - - element.focus(); - - dom.setStyles(originalStyles).on(element); - - if (win.scrollTo) { - // Some browser extensions unset this method to prevent annoyances - // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100 - // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1 - win.scrollTo(originalScrollLeft, originalScrollTop); - } - } - }; - - - wysihtml5.views.Composer.prototype.style = function() { - var that = this, - originalActiveElement = doc.querySelector(":focus"), - textareaElement = this.textarea.element, - hasPlaceholder = textareaElement.hasAttribute("placeholder"), - originalPlaceholder = hasPlaceholder && textareaElement.getAttribute("placeholder"); - this.focusStylesHost = this.focusStylesHost || HOST_TEMPLATE.cloneNode(false); - this.blurStylesHost = this.blurStylesHost || HOST_TEMPLATE.cloneNode(false); - - // Remove placeholder before copying (as the placeholder has an affect on the computed style) - if (hasPlaceholder) { - textareaElement.removeAttribute("placeholder"); - } - - if (textareaElement === originalActiveElement) { - textareaElement.blur(); - } - - // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) --------- - dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.iframe).andTo(this.blurStylesHost); - - // --------- editor styles --------- - dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost); - - // --------- apply standard rules --------- - dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument); - - // --------- :focus styles --------- - focusWithoutScrolling(textareaElement); - dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost); - dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost); - - // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus - // this is needed for when the change_view event is fired where the iframe is hidden and then - // the blur event fires and re-displays it - var boxFormattingStyles = wysihtml5.lang.array(BOX_FORMATTING).without(["display"]); - - // --------- restore focus --------- - if (originalActiveElement) { - originalActiveElement.focus(); - } else { - textareaElement.blur(); - } - - // --------- restore placeholder --------- - if (hasPlaceholder) { - textareaElement.setAttribute("placeholder", originalPlaceholder); - } - - // When copying styles, we only get the computed style which is never returned in percent unit - // Therefore we've to recalculate style onresize - if (!wysihtml5.browser.hasCurrentStyleProperty()) { - var winObserver = dom.observe(win, "resize", function() { - // Remove event listener if composer doesn't exist anymore - if (!dom.contains(document.documentElement, that.iframe)) { - winObserver.stop(); - return; - } - var originalTextareaDisplayStyle = dom.getStyle("display").from(textareaElement), - originalComposerDisplayStyle = dom.getStyle("display").from(that.iframe); - textareaElement.style.display = ""; - that.iframe.style.display = "none"; - dom.copyStyles(RESIZE_STYLE) - .from(textareaElement) - .to(that.iframe) - .andTo(that.focusStylesHost) - .andTo(that.blurStylesHost); - that.iframe.style.display = originalComposerDisplayStyle; - textareaElement.style.display = originalTextareaDisplayStyle; - }); - } - - // --------- Sync focus/blur styles --------- - this.parent.observe("focus:composer", function() { - dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.iframe); - dom.copyStyles(TEXT_FORMATTING) .from(that.focusStylesHost).to(that.element); - }); - - this.parent.observe("blur:composer", function() { - dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.iframe); - dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element); - }); - - return this; - }; -})(wysihtml5);/** - * Taking care of events - * - Simulating 'change' event on contentEditable element - * - Handling drag & drop logic - * - Catch paste events - * - Dispatch proprietary newword:composer event - * - Keyboard shortcuts - */ -(function(wysihtml5) { - var dom = wysihtml5.dom, - browser = wysihtml5.browser, - /** - * Map keyCodes to query commands - */ - shortcuts = { - "66": "bold", // B - "73": "italic", // I - "85": "underline" // U - }; - - wysihtml5.views.Composer.prototype.observe = function() { - var that = this, - state = this.getValue(), - iframe = this.sandbox.getIframe(), - element = this.element, - focusBlurElement = browser.supportsEventsInIframeCorrectly() ? element : this.sandbox.getWindow(), - // Firefox < 3.5 doesn't support the drop event, instead it supports a so called "dragdrop" event which behaves almost the same - pasteEvents = browser.supportsEvent("drop") ? ["drop", "paste"] : ["dragdrop", "paste"]; - - // --------- destroy:composer event --------- - dom.observe(iframe, "DOMNodeRemoved", function() { - clearInterval(domNodeRemovedInterval); - that.parent.fire("destroy:composer"); - }); - - // DOMNodeRemoved event is not supported in IE 8 - var domNodeRemovedInterval = setInterval(function() { - if (!dom.contains(document.documentElement, iframe)) { - clearInterval(domNodeRemovedInterval); - that.parent.fire("destroy:composer"); - } - }, 250); - - - // --------- Focus & blur logic --------- - dom.observe(focusBlurElement, "focus", function() { - that.parent.fire("focus").fire("focus:composer"); - - // Delay storing of state until all focus handler are fired - // especially the one which resets the placeholder - setTimeout(function() { state = that.getValue(); }, 0); - }); - - dom.observe(focusBlurElement, "blur", function() { - if (state !== that.getValue()) { - that.parent.fire("change").fire("change:composer"); - } - that.parent.fire("blur").fire("blur:composer"); - }); - - if (wysihtml5.browser.isIos()) { - // When on iPad/iPhone/IPod after clicking outside of editor, the editor loses focus - // but the UI still acts as if the editor has focus (blinking caret and onscreen keyboard visible) - // We prevent that by focusing a temporary input element which immediately loses focus - dom.observe(element, "blur", function() { - var input = element.ownerDocument.createElement("input"), - originalScrollTop = document.documentElement.scrollTop || document.body.scrollTop, - originalScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; - try { - that.selection.insertNode(input); - } catch(e) { - element.appendChild(input); - } - input.focus(); - input.parentNode.removeChild(input); - - window.scrollTo(originalScrollLeft, originalScrollTop); - }); - } - - // --------- Drag & Drop logic --------- - dom.observe(element, "dragenter", function() { - that.parent.fire("unset_placeholder"); - }); - - if (browser.firesOnDropOnlyWhenOnDragOverIsCancelled()) { - dom.observe(element, ["dragover", "dragenter"], function(event) { - event.preventDefault(); - }); - } - - dom.observe(element, pasteEvents, function(event) { - var dataTransfer = event.dataTransfer, - data; - - - console.log('PASTED', event); - if (dataTransfer && browser.supportsDataTransfer()) { - data = dataTransfer.getData("text/html") || dataTransfer.getData("text/plain"); - } - if (data) { - element.focus(); - that.commands.exec("insertHTML", data); - that.parent.fire("paste").fire("paste:composer"); - event.stopPropagation(); - event.preventDefault(); - } else { - setTimeout(function() { - that.parent.fire("paste").fire("paste:composer"); - }, 0); - } - }); - - // --------- neword event --------- - dom.observe(element, "keyup", function(event) { - var keyCode = event.keyCode; - if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) { - that.parent.fire("newword:composer"); - } - }); - - this.parent.observe("paste:composer", function() { - setTimeout(function() { that.parent.fire("newword:composer"); }, 0); - }); - - // --------- Make sure that images are selected when clicking on them --------- - if (!browser.canSelectImagesInContentEditable()) { - dom.observe(element, "mousedown", function(event) { - var target = event.target; - if (target.nodeName === "IMG") { - that.selection.selectNode(target); - event.preventDefault(); - } - }); - } - - // --------- Shortcut logic --------- - dom.observe(element, "keydown", function(event) { - var keyCode = event.keyCode, - command = shortcuts[keyCode]; - if ((event.ctrlKey || event.metaKey) && !event.altKey && command) { - that.commands.exec(command); - event.preventDefault(); - } - }); - - // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor --------- - dom.observe(element, "keydown", function(event) { - var target = that.selection.getSelectedNode(true), - keyCode = event.keyCode, - parent; - if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete - parent = target.parentNode; - // delete the <img> - parent.removeChild(target); - // and it's parent <a> too if it hasn't got any other child nodes - if (parent.nodeName === "A" && !parent.firstChild) { - parent.parentNode.removeChild(parent); - } - - setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0); - event.preventDefault(); - } - }); - - // --------- Show url in tooltip when hovering links or images --------- - var titlePrefixes = { - IMG: "Image: ", - A: "Link: " - }; - - dom.observe(element, "mouseover", function(event) { - var target = event.target, - nodeName = target.nodeName, - title; - if (nodeName !== "A" && nodeName !== "IMG") { - return; - } - var hasTitle = target.hasAttribute("title"); - if(!hasTitle){ - title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src")); - target.setAttribute("title", title); - } - }); - }; -})(wysihtml5);/** - * Class that takes care that the value of the composer and the textarea is always in sync - */ -(function(wysihtml5) { - var INTERVAL = 400; - - wysihtml5.views.Synchronizer = Base.extend( - /** @scope wysihtml5.views.Synchronizer.prototype */ { - - constructor: function(editor, textarea, composer) { - this.editor = editor; - this.textarea = textarea; - this.composer = composer; - - this._observe(); - }, - - /** - * Sync html from composer to textarea - * Takes care of placeholders - * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea - */ - fromComposerToTextarea: function(shouldParseHtml) { - this.textarea.setValue(wysihtml5.lang.string(this.composer.getValue()).trim(), shouldParseHtml); - }, - - /** - * Sync value of textarea to composer - * Takes care of placeholders - * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer - */ - fromTextareaToComposer: function(shouldParseHtml) { - var textareaValue = this.textarea.getValue(); - if (textareaValue) { - this.composer.setValue(textareaValue, shouldParseHtml); - } else { - this.composer.clear(); - this.editor.fire("set_placeholder"); - } - }, - - /** - * Invoke syncing based on view state - * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea - */ - sync: function(shouldParseHtml) { - if (this.editor.currentView.name === "textarea") { - this.fromTextareaToComposer(shouldParseHtml); - } else { - this.fromComposerToTextarea(shouldParseHtml); - } - }, - - /** - * Initializes interval-based syncing - * also makes sure that on-submit the composer's content is synced with the textarea - * immediately when the form gets submitted - */ - _observe: function() { - var interval, - that = this, - form = this.textarea.element.form, - startInterval = function() { - interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL); - }, - stopInterval = function() { - clearInterval(interval); - interval = null; - }; - - startInterval(); - - if (form) { - // If the textarea is in a form make sure that after onreset and onsubmit the composer - // has the correct state - wysihtml5.dom.observe(form, "submit", function() { - that.sync(true); - }); - wysihtml5.dom.observe(form, "reset", function() { - setTimeout(function() { that.fromTextareaToComposer(); }, 0); - }); - } - - this.editor.observe("change_view", function(view) { - if (view === "composer" && !interval) { - that.fromTextareaToComposer(true); - startInterval(); - } else if (view === "textarea") { - that.fromComposerToTextarea(true); - stopInterval(); - } - }); - - this.editor.observe("destroy:composer", stopInterval); - } - }); -})(wysihtml5); -wysihtml5.views.Textarea = wysihtml5.views.View.extend( - /** @scope wysihtml5.views.Textarea.prototype */ { - name: "textarea", - - constructor: function(parent, textareaElement, config) { - this.base(parent, textareaElement, config); - - this._observe(); - }, - - clear: function() { - this.element.value = ""; - }, - - getValue: function(parse) { - var value = this.isEmpty() ? "" : this.element.value; - if (parse) { - value = this.parent.parse(value); - } - return value; - }, - - setValue: function(html, parse) { - if (parse) { - html = this.parent.parse(html); - } - this.element.value = html; - }, - - hasPlaceholderSet: function() { - var supportsPlaceholder = wysihtml5.browser.supportsPlaceholderAttributeOn(this.element), - placeholderText = this.element.getAttribute("placeholder") || null, - value = this.element.value, - isEmpty = !value; - return (supportsPlaceholder && isEmpty) || (value === placeholderText); - }, - - isEmpty: function() { - return !wysihtml5.lang.string(this.element.value).trim() || this.hasPlaceholderSet(); - }, - - _observe: function() { - var element = this.element, - parent = this.parent, - eventMapping = { - focusin: "focus", - focusout: "blur" - }, - /** - * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events - * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai - */ - events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"]; - - parent.observe("beforeload", function() { - wysihtml5.dom.observe(element, events, function(event) { - var eventName = eventMapping[event.type] || event.type; - parent.fire(eventName).fire(eventName + ":textarea"); - }); - - wysihtml5.dom.observe(element, ["paste", "drop"], function() { - setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0); - }); - }); - } -});/** - * Toolbar Dialog - * - * @param {Element} link The toolbar link which causes the dialog to show up - * @param {Element} container The dialog container - * - * @example - * <!-- Toolbar link --> - * <a data-wysihtml5-command="insertImage">insert an image</a> - * - * <!-- Dialog --> - * <div data-wysihtml5-dialog="insertImage" style="display: none;"> - * <label> - * URL: <input data-wysihtml5-dialog-field="src" value="http://"> - * </label> - * <label> - * Alternative text: <input data-wysihtml5-dialog-field="alt" value=""> - * </label> - * </div> - * - * <script> - * var dialog = new wysihtml5.toolbar.Dialog( - * document.querySelector("[data-wysihtml5-command='insertImage']"), - * document.querySelector("[data-wysihtml5-dialog='insertImage']") - * ); - * dialog.observe("save", function(attributes) { - * // do something - * }); - * </script> - */ -(function(wysihtml5) { - var dom = wysihtml5.dom, - CLASS_NAME_OPENED = "wysihtml5-command-dialog-opened", - SELECTOR_FORM_ELEMENTS = "input, select, textarea", - SELECTOR_FIELDS = "[data-wysihtml5-dialog-field]", - ATTRIBUTE_FIELDS = "data-wysihtml5-dialog-field"; - - - wysihtml5.toolbar.Dialog = wysihtml5.lang.Dispatcher.extend( - /** @scope wysihtml5.toolbar.Dialog.prototype */ { - constructor: function(link, container) { - this.link = link; - this.container = container; - }, - - _observe: function() { - if (this._observed) { - return; - } - - var that = this, - callbackWrapper = function(event) { - var attributes = that._serialize(); - if (attributes == that.elementToChange) { - that.fire("edit", attributes); - } else { - that.fire("save", attributes); - } - that.hide(); - event.preventDefault(); - event.stopPropagation(); - }; - - dom.observe(that.link, "click", function(event) { - if (dom.hasClass(that.link, CLASS_NAME_OPENED)) { - setTimeout(function() { that.hide(); }, 0); - } - }); - - dom.observe(this.container, "keydown", function(event) { - var keyCode = event.keyCode; - if (keyCode === wysihtml5.ENTER_KEY) { - callbackWrapper(event); - } - if (keyCode === wysihtml5.ESCAPE_KEY) { - that.hide(); - } - }); - - dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper); - - dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) { - that.fire("cancel"); - that.hide(); - event.preventDefault(); - event.stopPropagation(); - }); - - var formElements = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS), - i = 0, - length = formElements.length, - _clearInterval = function() { clearInterval(that.interval); }; - for (; i<length; i++) { - dom.observe(formElements[i], "change", _clearInterval); - } - - this._observed = true; - }, - - /** - * Grabs all fields in the dialog and puts them in key=>value style in an object which - * then gets returned - */ - _serialize: function() { - var data = this.elementToChange || {}, - fields = this.container.querySelectorAll(SELECTOR_FIELDS), - length = fields.length, - i = 0; - for (; i<length; i++) { - data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; - } - return data; - }, - - /** - * Takes the attributes of the "elementToChange" - * and inserts them in their corresponding dialog input fields - * - * Assume the "elementToChange" looks like this: - * <a href="http://www.google.com" target="_blank">foo</a> - * - * and we have the following dialog: - * <input type="text" data-wysihtml5-dialog-field="href" value=""> - * <input type="text" data-wysihtml5-dialog-field="target" value=""> - * - * after calling _interpolate() the dialog will look like this - * <input type="text" data-wysihtml5-dialog-field="href" value="http://www.google.com"> - * <input type="text" data-wysihtml5-dialog-field="target" value="_blank"> - * - * Basically it adopted the attribute values into the corresponding input fields - * - */ - _interpolate: function(avoidHiddenFields) { - var field, - fieldName, - newValue, - focusedElement = document.querySelector(":focus"), - fields = this.container.querySelectorAll(SELECTOR_FIELDS), - length = fields.length, - i = 0; - for (; i<length; i++) { - field = fields[i]; - - // Never change elements where the user is currently typing in - if (field === focusedElement) { - continue; - } - - // Don't update hidden fields - // See https://github.com/xing/wysihtml5/pull/14 - if (avoidHiddenFields && field.type === "hidden") { - continue; - } - - fieldName = field.getAttribute(ATTRIBUTE_FIELDS); - newValue = this.elementToChange ? (this.elementToChange[fieldName] || "") : field.defaultValue; - field.value = newValue; - } - }, - - /** - * Show the dialog element - */ - show: function(elementToChange) { - var that = this, - firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS); - this.elementToChange = elementToChange; - this._observe(); - this._interpolate(); - if (elementToChange) { - this.interval = setInterval(function() { that._interpolate(true); }, 500); - } - dom.addClass(this.link, CLASS_NAME_OPENED); - this.container.style.display = ""; - this.fire("show"); - if (firstField && !elementToChange) { - try { - firstField.focus(); - } catch(e) {} - } - }, - - /** - * Hide the dialog element - */ - hide: function() { - clearInterval(this.interval); - this.elementToChange = null; - dom.removeClass(this.link, CLASS_NAME_OPENED); - this.container.style.display = "none"; - this.fire("hide"); - } - }); -})(wysihtml5); -/** - * Converts speech-to-text and inserts this into the editor - * As of now (2011/03/25) this only is supported in Chrome >= 11 - * - * Note that it sends the recorded audio to the google speech recognition api: - * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec - * - * Current HTML5 draft can be found here - * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html - * - * "Accessing Google Speech API Chrome 11" - * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/ - */ -(function(wysihtml5) { - var dom = wysihtml5.dom; - - var linkStyles = { - position: "relative" - }; - - var wrapperStyles = { - left: 0, - margin: 0, - opacity: 0, - overflow: "hidden", - padding: 0, - position: "absolute", - top: 0, - zIndex: 1 - }; - - var inputStyles = { - cursor: "inherit", - fontSize: "50px", - height: "50px", - marginTop: "-25px", - outline: 0, - padding: 0, - position: "absolute", - right: "-4px", - top: "50%" - }; - - var inputAttributes = { - "x-webkit-speech": "", - "speech": "" - }; - - wysihtml5.toolbar.Speech = function(parent, link) { - var input = document.createElement("input"); - if (!wysihtml5.browser.supportsSpeechApiOn(input)) { - link.style.display = "none"; - return; - } - - var wrapper = document.createElement("div"); - - wysihtml5.lang.object(wrapperStyles).merge({ - width: link.offsetWidth + "px", - height: link.offsetHeight + "px" - }); - - dom.insert(input).into(wrapper); - dom.insert(wrapper).into(link); - - dom.setStyles(inputStyles).on(input); - dom.setAttributes(inputAttributes).on(input) - - dom.setStyles(wrapperStyles).on(wrapper); - dom.setStyles(linkStyles).on(link); - - var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange"; - dom.observe(input, eventName, function() { - parent.execCommand("insertText", input.value); - input.value = ""; - }); - - dom.observe(input, "click", function(event) { - if (dom.hasClass(link, "wysihtml5-command-disabled")) { - event.preventDefault(); - } - - event.stopPropagation(); - }); - }; -})(wysihtml5);/** - * Toolbar - * - * @param {Object} parent Reference to instance of Editor instance - * @param {Element} container Reference to the toolbar container element - * - * @example - * <div id="toolbar"> - * <a data-wysihtml5-command="createLink">insert link</a> - * <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a> - * </div> - * - * <script> - * var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar")); - * </script> - */ -(function(wysihtml5) { - var CLASS_NAME_COMMAND_DISABLED = "wysihtml5-command-disabled", - CLASS_NAME_COMMANDS_DISABLED = "wysihtml5-commands-disabled", - CLASS_NAME_COMMAND_ACTIVE = "wysihtml5-command-active", - CLASS_NAME_ACTION_ACTIVE = "wysihtml5-action-active", - dom = wysihtml5.dom; - - wysihtml5.toolbar.Toolbar = Base.extend( - /** @scope wysihtml5.toolbar.Toolbar.prototype */ { - constructor: function(editor, container) { - this.editor = editor; - this.container = typeof(container) === "string" ? document.getElementById(container) : container; - this.composer = editor.composer; - - this._getLinks("command"); - this._getLinks("action"); - - this._observe(); - this.show(); - - var speechInputLinks = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"), - length = speechInputLinks.length, - i = 0; - for (; i<length; i++) { - new wysihtml5.toolbar.Speech(this, speechInputLinks[i]); - } - }, - - _getLinks: function(type) { - var links = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(), - length = links.length, - i = 0, - mapping = this[type + "Mapping"] = {}, - link, - group, - name, - value, - dialog; - for (; i<length; i++) { - link = links[i]; - name = link.getAttribute("data-wysihtml5-" + type); - value = link.getAttribute("data-wysihtml5-" + type + "-value"); - group = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']"); - dialog = this._getDialog(link, name); - - mapping[name + ":" + value] = { - link: link, - group: group, - name: name, - value: value, - dialog: dialog, - state: false - }; - } - }, - - _getDialog: function(link, command) { - var that = this, - dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"), - dialog, - caretBookmark; - - if (dialogElement) { - dialog = new wysihtml5.toolbar.Dialog(link, dialogElement); - - dialog.observe("show", function() { - caretBookmark = that.composer.selection.getBookmark(); - - that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); - }); - - dialog.observe("save", function(attributes) { - if (caretBookmark) { - that.composer.selection.setBookmark(caretBookmark); - } - that._execCommand(command, attributes); - - that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); - }); - - dialog.observe("cancel", function() { - that.editor.focus(false); - that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); - }); - } - return dialog; - }, - - /** - * @example - * var toolbar = new wysihtml5.Toolbar(); - * // Insert a <blockquote> element or wrap current selection in <blockquote> - * toolbar.execCommand("formatBlock", "blockquote"); - */ - execCommand: function(command, commandValue) { - if (this.commandsDisabled) { - return; - } - - var commandObj = this.commandMapping[command + ":" + commandValue]; - - // Show dialog when available - if (commandObj && commandObj.dialog && !commandObj.state) { - commandObj.dialog.show(); - } else { - this._execCommand(command, commandValue); - } - }, - - _execCommand: function(command, commandValue) { - // Make sure that composer is focussed (false => don't move caret to the end) - this.editor.focus(false); - - this.composer.commands.exec(command, commandValue); - this._updateLinkStates(); - }, - - execAction: function(action) { - var editor = this.editor; - switch(action) { - case "change_view": - if (editor.currentView === editor.textarea) { - editor.fire("change_view", "composer"); - } else { - editor.fire("change_view", "textarea"); - } - break; - } - }, - - _observe: function() { - var that = this, - editor = this.editor, - container = this.container, - links = this.commandLinks.concat(this.actionLinks), - length = links.length, - i = 0; - - for (; i<length; i++) { - // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied - // (you know, a:link { ... } doesn't match anchors with missing href attribute) - dom.setAttributes({ - href: "javascript:;", - unselectable: "on" - }).on(links[i]); - } - - // Needed for opera - dom.delegate(container, "[data-wysihtml5-command]", "mousedown", function(event) { event.preventDefault(); }); - - dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) { - var link = this, - command = link.getAttribute("data-wysihtml5-command"), - commandValue = link.getAttribute("data-wysihtml5-command-value"); - that.execCommand(command, commandValue); - event.preventDefault(); - }); - - dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) { - var action = this.getAttribute("data-wysihtml5-action"); - that.execAction(action); - event.preventDefault(); - }); - - editor.observe("focus:composer", function() { - that.bookmark = null; - clearInterval(that.interval); - that.interval = setInterval(function() { that._updateLinkStates(); }, 500); - }); - - editor.observe("blur:composer", function() { - clearInterval(that.interval); - }); - - editor.observe("destroy:composer", function() { - clearInterval(that.interval); - }); - - editor.observe("change_view", function(currentView) { - // Set timeout needed in order to let the blur event fire first - setTimeout(function() { - that.commandsDisabled = (currentView !== "composer"); - that._updateLinkStates(); - if (that.commandsDisabled) { - dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED); - } else { - dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED); - } - }, 0); - }); - }, - - _updateLinkStates: function() { - var element = this.composer.element, - commandMapping = this.commandMapping, - actionMapping = this.actionMapping, - i, - state, - action, - command; - // every millisecond counts... this is executed quite often - for (i in commandMapping) { - command = commandMapping[i]; - if (this.commandsDisabled) { - state = false; - dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); - if (command.group) { - dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE); - } - if (command.dialog) { - command.dialog.hide(); - } - } else { - state = this.composer.commands.state(command.name, command.value); - if (wysihtml5.lang.object(state).isArray()) { - // Grab first and only object/element in state array, otherwise convert state into boolean - // to avoid showing a dialog for multiple selected elements which may have different attributes - // eg. when two links with different href are selected, the state will be an array consisting of both link elements - // but the dialog interface can only update one - state = state.length === 1 ? state[0] : true; - } - dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED); - if (command.group) { - dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED); - } - } - - if (command.state === state) { - continue; - } - - command.state = state; - if (state) { - dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE); - if (command.group) { - dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE); - } - if (command.dialog) { - if (typeof(state) === "object") { - command.dialog.show(state); - } else { - command.dialog.hide(); - } - } - } else { - dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); - if (command.group) { - dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE); - } - if (command.dialog) { - command.dialog.hide(); - } - } - } - - for (i in actionMapping) { - action = actionMapping[i]; - - if (action.name === "change_view") { - action.state = this.editor.currentView === this.editor.textarea; - if (action.state) { - dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE); - } else { - dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE); - } - } - } - }, - - show: function() { - this.container.style.display = ""; - }, - - hide: function() { - this.container.style.display = "none"; - } - }); - -})(wysihtml5); -/** - * WYSIHTML5 Editor - * - * @param {Element} textareaElement Reference to the textarea which should be turned into a rich text interface - * @param {Object} [config] See defaultConfig object below for explanation of each individual config option - * - * @events - * load - * beforeload (for internal use only) - * focus - * focus:composer - * focus:textarea - * blur - * blur:composer - * blur:textarea - * change - * change:composer - * change:textarea - * paste - * paste:composer - * paste:textarea - * newword:composer - * destroy:composer - * undo:composer - * redo:composer - * beforecommand:composer - * aftercommand:composer - * change_view - */ -(function(wysihtml5) { - var undef; - - var defaultConfig = { - // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body - name: undef, - // Whether the editor should look like the textarea (by adopting styles) - style: true, - // Id of the toolbar element, pass falsey value if you don't want any toolbar logic - toolbar: undef, - // Whether urls, entered by the user should automatically become clickable-links - autoLink: true, - // Object which includes parser rules to apply when html gets inserted via copy & paste - // See parser_rules/*.js for examples - parserRules: { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} }, - // Parser method to use when the user inserts content via copy & paste - parser: wysihtml5.dom.parse, - // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option - composerClassName: "wysihtml5-editor", - // Class name to add to the body when the wysihtml5 editor is supported - bodyClassName: "wysihtml5-supported", - // Array (or single string) of stylesheet urls to be loaded in the editor's iframe - stylesheets: [], - // Placeholder text to use, defaults to the placeholder attribute on the textarea element - placeholderText: undef, - // Whether the composer should allow the user to manually resize images, tables etc. - allowObjectResizing: true, - // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5) - supportTouchDevices: true - }; - - wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend( - /** @scope wysihtml5.Editor.prototype */ { - constructor: function(textareaElement, config) { - this.textareaElement = typeof(textareaElement) === "string" ? document.getElementById(textareaElement) : textareaElement; - this.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get(); - this.textarea = new wysihtml5.views.Textarea(this, this.textareaElement, this.config); - this.currentView = this.textarea; - this._isCompatible = wysihtml5.browser.supported(); - - // Sort out unsupported/unwanted browsers here - if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) { - var that = this; - setTimeout(function() { that.fire("beforeload").fire("load"); }, 0); - return; - } - - // Add class name to body, to indicate that the editor is supported - wysihtml5.dom.addClass(document.body, this.config.bodyClassName); - - this.composer = new wysihtml5.views.Composer(this, this.textareaElement, this.config); - this.currentView = this.composer; - - if (typeof(this.config.parser) === "function") { - this._initParser(); - } - - this.observe("beforeload", function() { - this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer); - if (this.config.toolbar) { - this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar); - } - }); - - try { - console.log("Heya! This page is using wysihtml5 for rich text editing. Check out https://github.com/xing/wysihtml5"); - } catch(e) {} - }, - - isCompatible: function() { - return this._isCompatible; - }, - - clear: function() { - this.currentView.clear(); - return this; - }, - - getValue: function(parse) { - return this.currentView.getValue(parse); - }, - - setValue: function(html, parse) { - if (!html) { - return this.clear(); - } - this.currentView.setValue(html, parse); - return this; - }, - - focus: function(setToEnd) { - this.currentView.focus(setToEnd); - return this; - }, - - /** - * Deactivate editor (make it readonly) - */ - disable: function() { - this.currentView.disable(); - return this; - }, - - /** - * Activate editor - */ - enable: function() { - this.currentView.enable(); - return this; - }, - - isEmpty: function() { - return this.currentView.isEmpty(); - }, - - hasPlaceholderSet: function() { - return this.currentView.hasPlaceholderSet(); - }, - - parse: function(htmlOrElement) { - var returnValue = this.config.parser(htmlOrElement, this.config.parserRules, this.composer.sandbox.getDocument(), true); - if (typeof(htmlOrElement) === "object") { - wysihtml5.quirks.redraw(htmlOrElement); - } - return returnValue; - }, - - /** - * Prepare html parser logic - * - Observes for paste and drop - */ - _initParser: function() { - this.observe("paste:composer", function() { - var keepScrollPosition = true, - that = this; - that.composer.selection.executeAndRestore(function() { - wysihtml5.quirks.cleanPastedHTML(that.composer.element); - that.parse(that.composer.element); - }, keepScrollPosition); - }); - - this.observe("paste:textarea", function() { - var value = this.textarea.getValue(), - newValue; - newValue = this.parse(value); - this.textarea.setValue(newValue); - }); - } - }); -})(wysihtml5);