From: Stanislas Rolland Date: Sun, 6 Jan 2008 08:40:23 +0000 (+0000) Subject: * (major) Feature: Improved behaviour and configuration options for inline elements... X-Git-Tag: TYPO3_4-2-0alpha3~45 X-Git-Url: http://git.typo3.org/Packages/TYPO3.CMS.git/commitdiff_plain/38f1400bccd0a79bf0727cc00a44225c34bef4c9 * (major) Feature: Improved behaviour and configuration options for inline elements and text styling * Added feature 3708 (complete): support for * Fixed issue 5952 (complete): Usability Issue with classes wrapped with <> in select lists * Added feature 6034: Improvements of inlineCSS: adding classes to inline elements without additional span * Fixed issue 6839: sub and sup are added via CSS instead of tags in Safari git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@2865 709f56b5-9817-0410-a4d7-c38de5d9e867 --- diff --git a/ChangeLog b/ChangeLog index e8bce82f20e4..68d0f47ec4df 100755 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,12 @@ * Feature/Cleanup: Acronym plugin of htmlArea RTE using new plugin API and enabled in IE7 * Fixed issue 6154: Plugin Acronym enables class abbr for span * Added feature 1927: Activate Acronym Plugin for IE + * (major) Feature: Improved behaviour and configuration options for inline elements and text styling + * Added feature 3708 (complete): support for + * Fixed issue 5952 (complete): Usability Issue with classes wrapped with <> in select lists + * Added feature 6034: Improvements of inlineCSS: adding classes to inline elements without additional span + * Fixed issue 6839: sub and sup are added via CSS instead of tags in Safari + 2008-01-05 Stanislas Rolland diff --git a/typo3/sysext/rtehtmlarea/ChangeLog b/typo3/sysext/rtehtmlarea/ChangeLog index 9fc9456cbdb9..7457bc6f5326 100644 --- a/typo3/sysext/rtehtmlarea/ChangeLog +++ b/typo3/sysext/rtehtmlarea/ChangeLog @@ -3,6 +3,11 @@ * Feature/Cleanup: Acronym plugin of htmlArea RTE using new plugin API and enabled in IE7 * Fixed issue 6154: Plugin Acronym enables class abbr for span * Added feature 1927: Activate Acronym Plugin for IE + * (major) Feature: Improved behaviour and configuration options for inline elements and text styling + * Added feature 3708 (complete): support for + * Fixed issue 5952 (complete): Usability Issue with classes wrapped with <> in select lists + * Added feature 6034: Improvements of inlineCSS: adding classes to inline elements without additional span + * Fixed issue 6839: sub and sup are added via CSS instead of tags in Safari 2008-01-05 Stanislas Rolland diff --git a/typo3/sysext/rtehtmlarea/doc/manual.sxw b/typo3/sysext/rtehtmlarea/doc/manual.sxw index 6c3429ee07bb..69756b4634cb 100644 Binary files a/typo3/sysext/rtehtmlarea/doc/manual.sxw and b/typo3/sysext/rtehtmlarea/doc/manual.sxw differ diff --git a/typo3/sysext/rtehtmlarea/ext_localconf.php b/typo3/sysext/rtehtmlarea/ext_localconf.php index 7bb4caf73396..7ed167c31269 100644 --- a/typo3/sysext/rtehtmlarea/ext_localconf.php +++ b/typo3/sysext/rtehtmlarea/ext_localconf.php @@ -66,10 +66,10 @@ $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['Acronym'] = array(); $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['Acronym']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/Acronym/class.tx_rtehtmlarea_acronym.php:&tx_rtehtmlarea_acronym'; $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['Acronym']['addIconsToSkin'] = 0; $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['Acronym']['disableInFE'] = 1; -//$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['InlineElements'] = array(); -//$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['InlineElements']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php:&tx_rtehtmlarea_inlineelements'; -//$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['TextStyle'] = array(); -//$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['TextStyle']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php:&tx_rtehtmlarea_textstyle'; +$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['InlineElements'] = array(); +$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['InlineElements']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php:&tx_rtehtmlarea_inlineelements'; +$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['TextStyle'] = array(); +$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['TextStyle']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php:&tx_rtehtmlarea_textstyle'; $_EXTCONF = unserialize($_EXTCONF); // unserializing the configuration so we can use it here: diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php b/typo3/sysext/rtehtmlarea/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php new file mode 100644 index 000000000000..fecb4dbdb70f --- /dev/null +++ b/typo3/sysext/rtehtmlarea/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php @@ -0,0 +1,220 @@ + +* All rights reserved +* +* This script is part of the Typo3 project. The Typo3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/** + * InlineElements plugin for htmlArea RTE + * + * @author Stanislas Rolland + * + * TYPO3 CVS ID: $Id: class.tx_rtehtmlarea_pi1.php 2449 2007-08-15 08:12:19Z ingorenner $ + * + */ + +require_once(t3lib_extMgm::extPath('rtehtmlarea').'class.tx_rtehtmlareaapi.php'); + +class tx_rtehtmlarea_inlineelements extends tx_rtehtmlareaapi { + + protected $extensionKey = 'rtehtmlarea'; // The key of the extension that is extending htmlArea RTE + protected $pluginName = 'InlineElements'; // The name of the plugin registered by the extension + protected $relativePathToLocallangFile = 'extensions/InlineElements/locallang.xml'; // Path to this main locallang file of the extension relative to the extension dir. + protected $relativePathToSkin = 'extensions/InlineElements/skin/htmlarea.css'; // Path to the skin (css) file relative to the extension dir. + protected $htmlAreaRTE; // Reference to the invoking object + protected $thisConfig; // Reference to RTE PageTSConfig + protected $toolbar; // Reference to RTE toolbar array + protected $LOCAL_LANG; // Frontend language array + + protected $pluginButtons = 'formattext, bidioverride, big, bold, citation, code, definition, deletedtext, emphasis, insertedtext, italic, keyboard, quotation, sample, small, span, strikethrough, strong, subscript, superscript, underline, variable'; + + protected $convertToolbarForHtmlAreaArray = array ( + 'formattext' => 'FormatText', + 'bidioverride' => 'BiDiOverride', + 'big' => 'Big', + 'bold' => 'Bold', + 'citation' => 'Citation', + 'code' => 'Code', + 'definition' => 'Definition', + 'deletedtext' => 'DeletedText', + 'emphasis' => 'Emphasis', + 'insertedtext' => 'InsertedText', + 'italic' => 'Italic', + 'keyboard' => 'Keyboard', + 'monospaced' => 'MonoSpaced', + 'quotation' => 'Quotation', + 'sample' => 'Sample', + 'small' => 'Small', + 'span' => 'Span', + 'strikethrough' => 'StrikeThrough', + 'strong' => 'Strong', + 'subscript' => 'Subscript', + 'superscript' => 'Superscript', + 'underline' => 'Underline', + 'variable' => 'Variable', + ); + + private $defaultInlineElements = array( + 'none' => 'No markup', + 'b' => 'Bold', + 'bdo' => 'BiDi override', + 'big' => 'Large text', + 'cite' => 'Citation', + 'code' => 'Code', + 'del' => 'Deleted text', + 'dfn' => 'Definition', + 'em' => 'Emphasis', + 'i' => 'Italic', + 'ins' => 'Inserted text', + 'kbd' => 'Keyboard', + //'label' => 'Label', + 'q' => 'Quotation', + 'samp' => 'Sample', + 'small' => 'Small text', + 'span' => 'Style container', + 'strike' => 'Strike-through', + 'strong' => 'Strong emphasis', + 'sub' => 'Subscript', + 'sup' => 'Superscript', + 'tt' => 'Monospaced text', + 'u' => 'Underline', + 'var' => 'Variable', + ); + + private $defaultInlineElementsOrder = 'none, bidioverride, big, bold, citation, code, definition, deletedtext, emphasis, insertedtext, italic, keyboard, + monospaced, quotation, sample, small, span, strikethrough, strong, subscript, superscript, underline, variable'; + + private $buttonToInlineElement = array( + 'none' => 'none', + 'bidioverride' => 'bdo', + 'big' => 'big', + 'bold' => 'b', + 'citation' => 'cite', + 'code' => 'code', + 'definition' => 'dfn', + 'deletedtext' => 'del', + 'emphasis' => 'em', + 'insertedtext' => 'ins', + 'italic' => 'i', + 'keyboard' => 'kbd', + //'label' => 'label', + 'monospaced' => 'tt', + 'quotation' => 'q', + 'sample' => 'samp', + 'small' => 'small', + 'span' => 'span', + 'strikethrough' => 'strike', + 'strong' => 'strong', + 'subscript' => 'sub', + 'superscript' => 'sup', + 'underline' => 'u', + 'variable' => 'var', + ); + + /** + * Return JS configuration of the htmlArea plugins registered by the extension + * + * @param integer Relative id of the RTE editing area in the form + * + * @return string JS configuration for registered plugins + * + * The returned string will be a set of JS instructions defining the configuration that will be provided to the plugin(s) + * Each of the instructions should be of the form: + * RTEarea['.$RTEcounter.']["buttons"]["button-id"]["property"] = "value"; + */ + public function buildJavascriptConfiguration($RTEcounter) { + global $TSFE, $LANG; + + $registerRTEinJavascriptString = ''; + if (in_array('formattext', $this->toolbar)) { + if (!is_array( $this->thisConfig['buttons.']) || !is_array( $this->thisConfig['buttons.']['formattext.'])) { + $registerRTEinJavascriptString .= ' + RTEarea['.$RTEcounter.']["buttons"]["formattext"] = new Object();'; + } + + // Default inline elements + $hideItems = array(); + $restrictTo = array('*'); + $inlineElementsOrder = $this->defaultInlineElementsOrder; + $prefixLabelWithTag = false; + $postfixLabelWithTag = false; + + // Processing PageTSConfig + if (is_array($this->thisConfig['buttons.']) && is_array($this->thisConfig['buttons.']['formattext.'])) { + // Removing elements + if ($this->thisConfig['buttons.']['formattext.']['removeItems']) { + $hideItems = t3lib_div::trimExplode(',', $this->htmlAreaRTE->cleanList($this->thisConfig['buttons.']['formattext.']['removeItems']), 1); + } + // Restriction clause + if ($this->thisConfig['buttons.']['formattext.']['restrictToItems']) { + $restrictTo = t3lib_div::trimExplode(',', $this->htmlAreaRTE->cleanList('none,'.$this->thisConfig['buttons.']['formattext.']['restrictTo']), 1); + } + // Elements order + if ($this->thisConfig['buttons.']['formattext.']['orderItems']) { + $inlineElementsOrder = 'none,'.$this->thisConfig['buttons.']['formattext.']['orderItems']; + } + $prefixLabelWithTag = ($this->thisConfig['buttons.']['formattext.']['prefixLabelWithTag'])?true:$prefixLabelWithTag; + $postfixLabelWithTag = ($this->thisConfig['buttons.']['formattext.']['postfixLabelWithTag'])?true:$postfixLabelWithTag; + } + + $inlineElementsOrder = array_diff(t3lib_div::trimExplode(',', $this->htmlAreaRTE->cleanList($inlineElementsOrder), 1), $hideItems); + if (!in_array('*', $restrictTo)) { + $inlineElementsOrder = array_intersect($inlineElementsOrder, $restrictTo); + } + + // Localizing the options + $inlineElementsOptions = array(); + foreach ($inlineElementsOrder as $item) { + if ($this->htmlAreaRTE->is_FE()) { + $inlineElementsOptions[$this->buttonToInlineElement[$item]] = $TSFE->csConvObj->conv($TSFE->getLLL($this->defaultInlineElements[$this->buttonToInlineElement[$item]],$this->LOCAL_LANG), $TSFE->labelsCharset, $TSFE->renderCharset); + } else { + $inlineElementsOptions[$this->buttonToInlineElement[$item]] = $LANG->getLL($this->defaultInlineElements[$this->buttonToInlineElement[$item]]); + } + $inlineElementsOptions[$this->buttonToInlineElement[$item]] = (($prefixLabelWithTag && $item != 'none')?($this->buttonToInlineElement[$item].' - '):'') . $inlineElementsOptions[$this->buttonToInlineElement[$item]] . (($postfixLabelWithTag && $item != 'none')?(' - '.$this->buttonToInlineElement[$item]):''); + } + + $first = array_shift($inlineElementsOptions); + // Sorting the options + if (!is_array($this->thisConfig['buttons.']) || !is_array($this->thisConfig['buttons.']['formattext.']) || !$this->thisConfig['buttons.']['formattext.']['orderItems']) { + asort($inlineElementsOptions); + } + // Generating the javascript options + $JSInlineElements = '{ + "'. $first.'" : "none"'; + foreach ($inlineElementsOptions as $item => $label) { + $JSInlineElements .= ', + "' . $label . '" : "' . $item . '"'; + } + $JSInlineElements .= '};'; + + $registerRTEinJavascriptString .= ' + RTEarea['.$RTEcounter.'].buttons.formattext.dropDownOptions = '. $JSInlineElements; + } + return $registerRTEinJavascriptString; + } + + +} // end of class + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/extensions/InlineElements/class.tx_rtehtmlarea_inlineelements.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/locallang.xml b/typo3/sysext/rtehtmlarea/extensions/InlineElements/locallang.xml new file mode 100644 index 000000000000..440e550fb3af --- /dev/null +++ b/typo3/sysext/rtehtmlarea/extensions/InlineElements/locallang.xml @@ -0,0 +1,43 @@ + + + + Labels for Inline Elements plugin of htmlArea RTE + module + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/htmlarea.css b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/htmlarea.css new file mode 100644 index 000000000000..706dbda13333 --- /dev/null +++ b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/htmlarea.css @@ -0,0 +1,23 @@ +/* Selectors for the InlineElements plugin of htmlArea RTE */ +.htmlarea .toolbar .BiDiOverride {background-image:url("images/bidioverride.gif");} +.htmlarea .toolbar .Big {background-image:url("images/big.gif");} +.htmlarea .toolbar .Bold {background-image:url("images/bold.gif");} +.htmlarea .toolbar .Citation {background-image:url("images/citation.gif");} +.htmlarea .toolbar .Code {background-image:url("images/code.gif");} +.htmlarea .toolbar .Definition {background-image:url("images/definition.gif");} +.htmlarea .toolbar .DeletedText {background-image:url("images/deletedtext.gif");} +.htmlarea .toolbar .Emphasis {background-image:url("images/emphasis.gif");} +.htmlarea .toolbar .InsertedText {background-image:url("images/insertedtext.gif");} +.htmlarea .toolbar .Italic {background-image:url("images/italic.gif");} +.htmlarea .toolbar .Keyboard {background-image:url("images/keyboard.gif");} +.htmlarea .toolbar .MonoSpaced {background-image:url("images/monospaced.gif");} +.htmlarea .toolbar .Quotation {background-image:url("images/quotation.gif");} +.htmlarea .toolbar .Sample {background-image:url("images/sample.gif");} +.htmlarea .toolbar .Small {background-image:url("images/small.gif");} +.htmlarea .toolbar .Span {background-image:url("images/span.gif");} +.htmlarea .toolbar .StrikeThrough {background-image:url("images/strikethrough.gif");} +.htmlarea .toolbar .Strong {background-image:url("images/strong.gif");} +.htmlarea .toolbar .Subscript {background-image:url("images/subscript.gif");} +.htmlarea .toolbar .Superscript {background-image:url("images/superscript.gif");} +.htmlarea .toolbar .Underline {background-image:url("images/underline.gif");} +.htmlarea .toolbar .Variable {background-image:url("images/variable.gif");} diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/bidioverride.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/bidioverride.gif new file mode 100644 index 000000000000..46b502857053 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/bidioverride.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/big.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/big.gif new file mode 100644 index 000000000000..dd33ac227893 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/big.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/bold.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/bold.gif new file mode 100644 index 000000000000..10df93158004 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/bold.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/citation.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/citation.gif new file mode 100644 index 000000000000..4b4267ca3a54 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/citation.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/code.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/code.gif new file mode 100644 index 000000000000..05c4c7350308 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/code.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/definition.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/definition.gif new file mode 100644 index 000000000000..d0262282825c Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/definition.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/deletedtext.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/deletedtext.gif new file mode 100644 index 000000000000..9cbe8716d956 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/deletedtext.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/emphasis.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/emphasis.gif new file mode 100644 index 000000000000..d87caf51f8e5 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/emphasis.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/insertedtext.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/insertedtext.gif new file mode 100644 index 000000000000..6d33f9888bde Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/insertedtext.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/italic.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/italic.gif new file mode 100644 index 000000000000..b118ec1456e0 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/italic.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/keyboard.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/keyboard.gif new file mode 100644 index 000000000000..9f56ce595051 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/keyboard.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/monospaced.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/monospaced.gif new file mode 100644 index 000000000000..93b586a05cf3 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/monospaced.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/quotation.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/quotation.gif new file mode 100644 index 000000000000..2ae3f9d8de09 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/quotation.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/sample.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/sample.gif new file mode 100644 index 000000000000..efefac9577dd Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/sample.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/small.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/small.gif new file mode 100644 index 000000000000..f222a2a84b32 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/small.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/span.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/span.gif new file mode 100644 index 000000000000..f09920dd3bc5 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/span.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/strikethrough.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/strikethrough.gif new file mode 100644 index 000000000000..e533be73bf9e Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/strikethrough.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/strong.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/strong.gif new file mode 100644 index 000000000000..b78fc9c7f518 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/strong.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/subscript.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/subscript.gif new file mode 100644 index 000000000000..dbd399c5be32 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/subscript.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/superscript.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/superscript.gif new file mode 100644 index 000000000000..1a5e4d0af4af Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/superscript.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/underline.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/underline.gif new file mode 100644 index 000000000000..e65b215aefb5 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/underline.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/variable.gif b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/variable.gif new file mode 100644 index 000000000000..9f955f863450 Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/InlineElements/skin/images/variable.gif differ diff --git a/typo3/sysext/rtehtmlarea/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php b/typo3/sysext/rtehtmlarea/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php new file mode 100644 index 000000000000..bcae77f8844f --- /dev/null +++ b/typo3/sysext/rtehtmlarea/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php @@ -0,0 +1,61 @@ + +* All rights reserved +* +* This script is part of the Typo3 project. The Typo3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/** + * TextStyle plugin for htmlArea RTE + * + * @author Stanislas Rolland + * + * TYPO3 SVN ID: $Id$ + * + */ + +require_once(t3lib_extMgm::extPath('rtehtmlarea').'class.tx_rtehtmlareaapi.php'); + +class tx_rtehtmlarea_textstyle extends tx_rtehtmlareaapi { + + protected $extensionKey = 'rtehtmlarea'; // The key of the extension that is extending htmlArea RTE + protected $pluginName = 'TextStyle'; // The name of the plugin registered by the extension + protected $relativePathToLocallangFile = 'extensions/TextStyle/locallang.xml'; // Path to this main locallang file of the extension relative to the extension dir. + protected $relativePathToSkin = ''; // Path to the skin (css) file relative to the extension dir. + protected $htmlAreaRTE; // Reference to the invoking object + protected $thisConfig; // Reference to RTE PageTSConfig + protected $toolbar; // Reference to RTE toolbar array + protected $LOCAL_LANG; // Frontend language array + + protected $pluginButtons = 'textstyle'; // The comma-seperated list of button names that the extension id adding to the htmlArea RTE tollbar + protected $pluginLabels = 'textstylelabel'; // The comma-seperated list of label names that the extension id adding to the htmlArea RTE tollbar + // The name-converting array, converting the button names used in the RTE PageTSConfing to the button id's used by the JS scripts + protected $convertToolbarForHtmlAreaArray = array ( + 'textstylelabel' => 'I[text_style]', + 'textstyle' => 'TextStyle', + ); + protected $requiresClassesConfiguration = true; // True if the extension requires the PageTSConfig Classes configuration + +} // end of class + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/extensions/TextStyle/class.tx_rtehtmlarea_textstyle.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/rtehtmlarea/extensions/TextStyle/locallang.xml b/typo3/sysext/rtehtmlarea/extensions/TextStyle/locallang.xml new file mode 100644 index 000000000000..7fda2e48d3f7 --- /dev/null +++ b/typo3/sysext/rtehtmlarea/extensions/TextStyle/locallang.xml @@ -0,0 +1,20 @@ + + + + Labels for Text Style plugin of htmlArea RTE + module + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/inline-elements.js b/typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/inline-elements.js new file mode 100644 index 000000000000..8d4965a8f310 --- /dev/null +++ b/typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/inline-elements.js @@ -0,0 +1,529 @@ +/*************************************************************** +* Copyright notice +* +* (c) 2007-2008 Stanislas Rolland +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/* + * Inline Elements Plugin for TYPO3 htmlArea RTE + * + * TYPO3 SVN ID: $Id$ + */ +/* + * Creation of the class of InlineElements plugins + */ +InlineElements = HTMLArea.Plugin.extend({ + /* + * Let the base class do some initialization work + */ + constructor : function(editor, pluginName) { + this.base(editor, pluginName); + }, + + /* + * This function gets called by the base constructor + */ + configurePlugin : function (editor) { + + this.allowedAttributes = new Array("id", "title", "lang", "xml:lang", "dir", (HTMLArea.is_gecko?"class":"className")); + + if (this.editorConfiguration.buttons.textstyle) { + this.tags = this.editorConfiguration.buttons.textstyle.tags; + } + + /* + * Registering plugin "About" information + */ + var pluginInformation = { + version : "1.0", + developer : "Stanislas Rolland", + developerUrl : "http://www.fructifor.ca/", + copyrightOwner : "Stanislas Rolland", + sponsor : this.localize("Technische Universitat Ilmenau"), + sponsorUrl : "http://www.tu-ilmenau.de/", + license : "GPL" + }; + this.registerPluginInformation(pluginInformation); + + /* + * Registering the dropdown list + */ + var buttonId = "FormatText"; + var dropDownConfiguration = { + id : buttonId, + tooltip : this.localize(buttonId + "-Tooltip"), + options : (this.editorConfiguration.buttons[buttonId.toLowerCase()]?this.editorConfiguration.buttons[buttonId.toLowerCase()]["dropDownOptions"]:null), + action : "onChange", + refresh : null + }; + this.registerDropDown(dropDownConfiguration); + + /* + * Registering the buttons + */ + var n = this.buttonList.length; + for (var i = 0; i < n; ++i) { + var button = this.buttonList[i]; + buttonId = button[0]; + var buttonConfiguration = { + id : buttonId, + tooltip : this.localize(buttonId + "-Tooltip"), + action : "onButtonPress", + context : button[1], + hide : false, + selection : false + }; + this.registerButton(buttonConfiguration); + } + }, + + /* + * The list of buttons added by this plugin + */ + buttonList : [ + ["BiDiOverride", null], + ["Big", null], + ["Bold", null], + ["Citation", null], + ["Code", null], + ["Definition", null], + ["DeletedText", null], + ["Emphasis", null], + ["InsertedText", null], + ["Italic", null], + ["Keyboard", null], + //["Label", null], + ["MonoSpaced", null], + ["Quotation", null], + ["Sample", null], + ["Small", null], + ["Span", null], + ["StrikeThrough", null], + ["Strong", null], + ["Subscript", null], + ["Superscript", null], + ["Underline", null], + ["Variable", null] + ], + + /* + * Conversion object: button names to corresponding tag names + */ + convertBtn : { + BiDiOverride : "bdo", + Big : "big", + Bold : "b", + Citation : "cite", + Code : "code", + Definition : "dfn", + DeletedText : "del", + Emphasis : "em", + InsertedText : "ins", + Italic : "i", + Keyboard : "kbd", + //Label : "label", + MonoSpaced : "tt", + Quotation : "q", + Sample : "samp", + Small : "small", + Span : "span", + StrikeThrough : "strike", + Strong : "strong", + Subscript : "sub", + Superscript : "sup", + Underline : "u", + Variable : "var" + }, + + /* + * Regular expression to check if an element is an inline elment + */ + REInlineElements : /^(b|bdo|big|cite|code|del|dfn|em|i|ins|kbd|label|q|samp|small|span|strike|strong|sub|sup|tt|u|var)$/, + + /* + * Function to check if an element is an inline elment + */ + isInlineElement : function (el) { + return el && (el.nodeType === 1) && this.REInlineElements.test(el.nodeName.toLowerCase()); + }, + + /* + * This function gets called when some inline element button was pressed. + */ + onButtonPress : function (editor, id) { + // Could be a button or its hotkey + var buttonId = this.translateHotKey(id); + buttonId = buttonId ? buttonId : id; + var obj = editor._toolbarObjects[buttonId]; + var element = this.convertBtn[buttonId]; + if (element) { + this.applyInlineElement(editor, element); + return false; + } else { + this.appendToLog("onButtonPress", "No element corresponding to button: " + buttonId); + } + }, + + /* + * This function gets called when some inline element was selected in the drop-down list + */ + onChange : function (editor, buttonId) { + var tbobj = editor._toolbarObjects[buttonId]; + var element = document.getElementById(tbobj.elementId).value; + this.applyInlineElement(editor, element, false); + }, + + /* + * This function applies to the selection the markup chosen in the drop-down list or corresponding to the button pressed + */ + applyInlineElement : function (editor, element) { + editor.focusEditor(); + var selection = editor._getSelection(); + var range = editor._createRange(selection); + var parent = editor.getParentElement(selection, range); + var ancestors = editor.getAllAncestors(); + var elementIsAncestor = false; + var selectionEmpty = editor._selectionEmpty(selection); + if (HTMLArea.is_ie) { + var bookmark = range.getBookmark(); + } + // Check if the chosen element is among the ancestors + for (var i = 0; i < ancestors.length; ++i) { + if ((ancestors[i].nodeType == 1) && (ancestors[i].nodeName.toLowerCase() == element)) { + elementIsAncestor = true; + var elementAncestorIndex = i; + break; + } + } + if (!selectionEmpty) { + // The selection is not empty. + for (var i = 0; i < ancestors.length; ++i) { + fullNodeSelected = (HTMLArea.is_ie && ((editor._statusBarTree.selected === ancestors[i] && ancestors[i].innerText === range.text) || (!editor._statusBarTree.selected && ancestors[i].innerText === range.text))) + || (HTMLArea.is_gecko && ((editor._statusBarTree.selected === ancestors[i] && ancestors[i].textContent === range.toString()) || (!editor._statusBarTree.selected && ancestors[i].textContent === range.toString()))); + if (fullNodeSelected) { + if (!HTMLArea.isBlockElement(ancestors[i])) { + parent = ancestors[i]; + } + break; + } + } + // Working around bug in Safari selectNodeContents + if (!fullNodeSelected && HTMLArea.is_safari && this.editor._statusBarTree.selected && this.isInlineElement(this.editor._statusBarTree.selected) && this.editor._statusBarTree.selected.textContent === range.toString()) { + fullNodeSelected = true; + parent = this.editor._statusBarTree.selected; + } + + var fullNodeTextSelected = (HTMLArea.is_gecko && parent.textContent === range.toString()) + || (HTMLArea.is_ie && parent.innerText === range.text); + if (fullNodeTextSelected && elementIsAncestor) { + fullNodeSelected = true; + } + if (element !== "none" && !(fullNodeSelected && elementIsAncestor)) { + // Add markup + if (HTMLArea.is_gecko) { + if (fullNodeSelected && editor._statusBarTree.selected) { + if (HTMLArea.is_safari) { + this.editor.selectNode(parent); + range = this.editor._createRange(this.editor._getSelection()); + } else { + range.selectNode(parent); + } + } + var newElement = this.editor._doc.createElement(element); + if (element === "bdo") { + newElement.setAttribute("dir", "rtl"); + } + // Sometimes Opera 9.25 raises a bad boundary points error + if (HTMLArea.is_opera) { + try { + range.surroundContents(newElement); + } catch(e) { + newElement.appendChild(range.extractContents()); + range.insertNode(newElement); + } + } else { + range.surroundContents(newElement); + } + // Sometimes Firefox inserts empty elements just outside the boundaries of the range + var neighbour = newElement.previousSibling; + if (neighbour && (neighbour.nodeType != 3) && !/\S/.test(neighbour.textContent)) { + HTMLArea.removeFromParent(neighbour); + } + neighbour = newElement.nextSibling; + if (neighbour && (neighbour.nodeType != 3) && !/\S/.test(neighbour.textContent)) { + HTMLArea.removeFromParent(neighbour); + } + if (fullNodeSelected && editor._statusBarTree.selected && !HTMLArea.is_safari) { + this.editor.selectNodeContents(newElement.lastChild, false); + } else { + this.editor.selectNodeContents(newElement, false); + } + range.detach(); + } else { + var tagopen = "<" + element + ">"; + var tagclose = ""; + if (fullNodeSelected) { + if (!editor._statusBarTree.selected) { + parent.innerHTML = tagopen + parent.innerHTML + tagclose; + if (element === "bdo") { + parent.firstChild.setAttribute("dir", "rtl"); + } + editor.selectNodeContents(parent, false); + } else { + var content = parent.outerHTML; + var newElement = this.remapMarkup(parent, element); + newElement.innerHTML = content; + editor.selectNodeContents(newElement, false); + } + } else { + var rangeStart = range.duplicate(); + rangeStart.collapse(true); + var parentStart = rangeStart.parentElement(); + var rangeEnd = range.duplicate(); + rangeEnd.collapse(true); + var newRange = editor._createRange(); + + var parentEnd = rangeEnd.parentElement(); + var upperParentStart = parentStart; + if (parentStart !== parent) { + while (upperParentStart.parentNode !== parent) { + upperParentStart = upperParentStart.parentNode; + } + } + + var newElement = editor._doc.createElement(element); + newElement.innerHTML = range.htmlText; + // IE eats spaces on the start boundary + if (range.htmlText.charAt(0) === "\x20") { + newElement.innerHTML = " " + newElement.innerHTML; + } + var newElementClone = newElement.cloneNode(true); + range.pasteHTML(newElement.outerHTML); + // IE inserts the element as the last child of the start container + if (parentStart !== parent + && parentStart.lastChild + && parentStart.lastChild.nodeType === 1 + && parentStart.lastChild.nodeName.toLowerCase() === element) { + parent.insertBefore(newElementClone, upperParentStart.nextSibling); + parentStart.removeChild(parentStart.lastChild); + // Sometimes an empty previous sibling was created + if (newElementClone.previousSibling + && newElementClone.previousSibling.nodeType === 1 + && !newElementClone.previousSibling.innerText) { + parent.removeChild(newElementClone.previousSibling); + } + // The bookmark will not work anymore + newRange.moveToElementText(newElementClone); + newRange.collapse(false); + newRange.select(); + } else { + // Working around IE boookmark bug + if (parentStart != parentEnd) { + var newRange = editor._createRange(); + if (newRange.moveToBookmark(bookmark)) { + newRange.collapse(false); + newRange.select(); + } + } else { + range.collapse(false); + } + } + parent.normalize(); + } + } + } else { + // A complete node is selected: remove the markup + if (fullNodeSelected) { + if (elementIsAncestor) { + parent = ancestors[elementAncestorIndex]; + } + this.removeMarkup(parent); + } + } + } else { + // Remove or remap markup when the selection is collapsed + if (parent && !HTMLArea.isBlockElement(parent)) { + if ((element === "none") || elementIsAncestor) { + if (elementIsAncestor) { + parent = ancestors[elementAncestorIndex]; + } + this.removeMarkup(parent); + } else { + var bookmark = this.editor.getBookmark(range); + var newElement = this.remapMarkup(parent, element); + this.editor.selectRange(this.editor.moveToBookmark(bookmark)); + } + } + } + }, + + /* + * This function remaps the given element to the specified tagname + */ + remapMarkup : function(element, tagName) { + var attributeValue; + var newElement = this.editor.convertNode(element, tagName); + if (tagName === "bdo") { + newElement.setAttribute("dir", "ltr"); + } + for (var i = 0; i < this.allowedAttributes.length; ++i) { + if (attributeValue = element.getAttribute(this.allowedAttributes[i])) { + newElement.setAttribute(this.allowedAttributes[i], attributeValue); + } + } + + if (this.tags && this.tags[tagName] && this.tags[tagName].allowedClasses) { + if (newElement.className && /\S/.test(newElement.className)) { + var allowedClasses = new RegExp( "^(" + this.tags[tagName].allowedClasses.trim().split(",").join("|") + ")$"); + classNames = newElement.className.trim().split(" "); + for (var i = 0; i < classNames.length; ++i) { + if (!allowedClasses.test(classNames[i])) { + HTMLArea._removeClass(newElement, classNames[i]); + } + } + } + } + return newElement; + }, + + /* + * This function removes the given markup element + */ + removeMarkup : function(element) { + var bookmark = this.editor.getBookmark(this.editor._createRange(this.editor._getSelection())); + var parent = element.parentNode; + while (element.firstChild) { + parent.insertBefore(element.firstChild, element); + } + parent.removeChild(element); + this.editor.selectRange(this.editor.moveToBookmark(bookmark)); + }, + + /* + * This function gets called when the toolbar is updated + */ + onUpdateToolbar : function () { + var editor = this.editor; + if (editor._editMode !== "textmode") { + var id, activeButton; + var tagName = false, endPointsInSameBlock = true, fullNodeSelected = false; + var sel = editor._getSelection(); + var range = editor._createRange(sel); + var parent = editor.getParentElement(sel); + if (parent && !HTMLArea.isBlockElement(parent)) { + tagName = parent.nodeName.toLowerCase(); + } + var selectionEmpty = editor._selectionEmpty(sel); + if (!selectionEmpty) { + var ancestors = editor.getAllAncestors(); + for (var i = 0; i < ancestors.length; ++i) { + fullNodeSelected = (editor._statusBarTree.selected === ancestors[i]) + && ((HTMLArea.is_gecko && ancestors[i].textContent === range.toString()) || (HTMLArea.is_ie && ancestors[i].innerText === range.text)); + if (fullNodeSelected) { + if (!HTMLArea.isBlockElement(ancestors[i])) { + tagName = ancestors[i].nodeName.toLowerCase(); + } + break; + } + } + // Working around bug in Safari selectNodeContents + if (!fullNodeSelected && HTMLArea.is_safari && this.editor._statusBarTree.selected && this.isInlineElement(this.editor._statusBarTree.selected) && this.editor._statusBarTree.selected.textContent === range.toString()) { + fullNodeSelected = true; + tagName = this.editor._statusBarTree.selected.nodeName.toLowerCase(); + } + } + var selectionInInlineElement = tagName && this.REInlineElements.test(tagName); + var disabled = !this.endPointsInSameBlock() || (fullNodeSelected && !tagName) || (selectionEmpty && !selectionInInlineElement); + + var obj = editor.config.customSelects["FormatText"]; + if ((typeof(obj) !== "undefined") && (typeof(editor._toolbarObjects[obj.id]) !== "undefined")) { + this.updateValue(editor, obj, tagName, selectionEmpty, fullNodeSelected, disabled); + } + + var ancestors = editor.getAllAncestors(); + var bl = this.buttonList; + for (var i = 0; i < bl.length; ++i) { + var btn = bl[i]; + id = btn[0]; + var obj = editor._toolbarObjects[id]; + if ((typeof(obj) !== "undefined")) { + activeButton = false; + for (var j = ancestors.length; --j >= 0;) { + var el = ancestors[j]; + if (!el) { continue; } + if (this.convertBtn[id] === el.nodeName.toLowerCase()) { + activeButton = true; + } + } + obj.state("active", activeButton); + obj.state("enabled", !disabled); + } + } + } + }, + + /* + * This function determines if the end poins of the current selection are within the same block + */ + endPointsInSameBlock : function() { + var selection = this.editor._getSelection(); + if (this.editor._selectionEmpty(selection)) { + return true; + } else { + var parent = this.editor.getParentElement(selection); + var endBlocks = this.editor.getEndBlocks(selection); + return (endBlocks.start === endBlocks.end && !/^(body|table|thead|tbody|tfoot|tr)$/i.test(parent.nodeName)); + } + }, + + /* + * This function updates the drop-down list of inline elemenents + */ + updateValue : function (editor, obj, tagName, selectionEmpty, fullNodeSelected, disabled) { + var select = document.getElementById(editor._toolbarObjects[obj.id]["elementId"]); + var options = select.options; + for (var i = options.length; --i >= 0;) { + options[i].selected = false; + } + select.selectedIndex = 0; + options[0].selected = true; + select.options[0].text = this.localize("No markup"); + for (i = options.length; --i >= 0;) { + if (tagName === options[i].value) { + if (selectionEmpty || fullNodeSelected) { + options[i].selected = true; + select.selectedIndex = i; + select.options[0].text = this.localize("Remove markup"); + } + break; + } + } + + select.disabled = !(options.length>1) || disabled; + select.className = ""; + if (select.disabled) { + select.className = "buttonDisabled"; + } + } +}); + diff --git a/typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/locallang.xml b/typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/locallang.xml new file mode 100644 index 000000000000..0856febba497 --- /dev/null +++ b/typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/locallang.xml @@ -0,0 +1,46 @@ + + + + + Labels for Inline Elements plugin of htmlArea RTE + module + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/rtehtmlarea/htmlarea/plugins/TextStyle/locallang.xml b/typo3/sysext/rtehtmlarea/htmlarea/plugins/TextStyle/locallang.xml new file mode 100644 index 000000000000..3c041dbf7f5f --- /dev/null +++ b/typo3/sysext/rtehtmlarea/htmlarea/plugins/TextStyle/locallang.xml @@ -0,0 +1,26 @@ + + + + + Labels for Text Style plugin of htmlArea RTE + module + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/rtehtmlarea/htmlarea/plugins/TextStyle/text-style.js b/typo3/sysext/rtehtmlarea/htmlarea/plugins/TextStyle/text-style.js new file mode 100644 index 000000000000..26752ed5a8da --- /dev/null +++ b/typo3/sysext/rtehtmlarea/htmlarea/plugins/TextStyle/text-style.js @@ -0,0 +1,636 @@ +/*************************************************************** +* Copyright notice +* +* (c) 2007 Stanislas Rolland +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/* + * Text Style Plugin for TYPO3 htmlArea RTE + * + * TYPO3 SVN ID: $Id$ + */ +/* + * Creation of the class of TextStyle plugins + */ +TextStyle = HTMLArea.Plugin.extend({ + /* + * Let the base class do some initialization work + */ + constructor : function(editor, pluginName) { + this.base(editor, pluginName); + }, + + /* + * This function gets called by the class constructor + */ + configurePlugin : function (editor) { + + this.cssLoaded = false; + this.cssTimeout = null; + this.cssParseCount = 0; + this.cssArray = new Object(); + + this.classesUrl = this.editorConfiguration.classesUrl; + this.pageTSconfiguration = this.editorConfiguration.buttons.textstyle; + this.tags = this.pageTSconfiguration.tags; + if (!this.tags) { + this.tags = new Object(); + } + if (typeof(this.editorConfiguration.classesTag) !== "undefined") { + if (this.editorConfiguration.classesTag.span) { + if (!this.tags.span) { + this.tags.span = new Object(); + } + if (!this.tags.span.allowedClasses) { + this.tags.span.allowedClasses = this.editorConfiguration.classesTag.span; + } + } + } + this.showTagFreeClasses = this.pageTSconfiguration.showTagFreeClasses || this.editorConfiguration.showTagFreeClasses; + this.prefixLabelWithClassName = this.pageTSconfiguration.prefixLabelWithClassName; + this.postfixLabelWithClassName = this.pageTSconfiguration.postfixLabelWithClassName; + + /* + * Regular expression to check if an element is an inline elment + */ + this.REInlineTags = /^(abbr|acronym|b|bdo|big|cite|code|del|dfn|em|i|ins|kbd|q|samp|small|span|strike|strong|sub|sup|tt|u|var)$/; + + /* + * Registering plugin "About" information + */ + var pluginInformation = { + version : "1.0", + developer : "Stanislas Rolland", + developerUrl : "http://www.fructifor.ca/", + copyrightOwner : "Stanislas Rolland", + sponsor : this.localize("Technische Universitat Ilmenau"), + sponsorUrl : "http://www.tu-ilmenau.de/", + license : "GPL" + }; + this.registerPluginInformation(pluginInformation); + + /* + * Registering the dropdown list + */ + var buttonId = "TextStyle"; + var dropDownConfiguration = { + id : buttonId, + tooltip : this.localize(buttonId + "-Tooltip"), + textMode : false, + options : {"":""}, + action : "onChange", + refresh : "generate", + context : null + }; + this.registerDropDown(dropDownConfiguration); + + return true; + }, + + isInlineElement : function (el) { + return el && (el.nodeType === 1) && this.REInlineTags.test(el.nodeName.toLowerCase()); + }, + + /* + * This function gets called when some style in the drop-down list applies it to the highlighted textt + */ + onChange : function (editor, buttonId) { + var select = document.getElementById(this.editor._toolbarObjects[buttonId].elementId); + var className = select.value; + var classNames = null; + var fullNodeSelected = false; + + this.editor.focusEditor(); + var selection = this.editor._getSelection(); + var range = this.editor._createRange(selection); + var parent = this.editor.getParentElement(); + var selectionEmpty = this.editor._selectionEmpty(selection); + var ancestors = this.editor.getAllAncestors(); + if (HTMLArea.is_ie) { + var bookmark = range.getBookmark(); + } + + if (!selectionEmpty) { + // The selection is not empty + for (var i = 0; i < ancestors.length; ++i) { + fullNodeSelected = (HTMLArea.is_ie && ((this.editor._statusBarTree.selected === ancestors[i] && ancestors[i].innerText === range.text) || (!this.editor._statusBarTree.selected && ancestors[i].innerText === range.text))) + || (HTMLArea.is_gecko && ((this.editor._statusBarTree.selected === ancestors[i] && ancestors[i].textContent === range.toString()) || (!this.editor._statusBarTree.selected && ancestors[i].textContent === range.toString()))); + if (fullNodeSelected) { + if (this.isInlineElement(ancestors[i])) { + parent = ancestors[i]; + } + break; + } + } + // Working around bug in Safari selectNodeContents + if (!fullNodeSelected && HTMLArea.is_safari && this.editor._statusBarTree.selected && this.isInlineElement(this.editor._statusBarTree.selected) && this.editor._statusBarTree.selected.textContent === range.toString()) { + fullNodeSelected = true; + parent = this.editor._statusBarTree.selected; + } + } + if (!selectionEmpty && !fullNodeSelected) { + // The selection is not empty, nor full element + if (className !== "none") { + // Add span element with class attribute + if (HTMLArea.is_gecko) { + var newElement = this.editor._doc.createElement("span"); + HTMLArea._addClass(newElement, className); + range.surroundContents(newElement); + newElement.normalize(); + parent.normalize(); + // Firefox sometimes inserts empty elements just outside the boundaries of the range + var neighbour = newElement.previousSibling; + if (neighbour && (neighbour.nodeType != 3) && !/\S/.test(neighbour.textContent)) { + HTMLArea.removeFromParent(neighbour); + } + neighbour = newElement.nextSibling; + if (neighbour && (neighbour.nodeType != 3) && !/\S/.test(neighbour.textContent)) { + HTMLArea.removeFromParent(neighbour); + } + this.editor.selectNodeContents(newElement, false); + range.detach(); + } else { + var rangeStart = range.duplicate(); + rangeStart.collapse(true); + var parentStart = rangeStart.parentElement(); + var rangeEnd = range.duplicate(); + rangeEnd.collapse(true); + var parentEnd = rangeEnd.parentElement(); + var newRange = editor._createRange(); + + var upperParentStart = parentStart; + if (parentStart !== parent) { + while (upperParentStart.parentNode !== parent) { + upperParentStart = upperParentStart.parentNode; + } + } + + var newElement = editor._doc.createElement("span"); + HTMLArea._addClass(newElement, className); + newElement.innerHTML = range.htmlText; + // IE eats spaces on the start boundary + if (range.htmlText.charAt(0) === "\x20") { + newElement.innerHTML = " " + newElement.innerHTML; + } + var newElementClone = newElement.cloneNode(true); + range.pasteHTML(newElement.outerHTML); + // IE inserts the element as the last child of the start container + if (parentStart !== parent + && parentStart.lastChild + && parentStart.lastChild.nodeType === 1 + && parentStart.lastChild.nodeName.toLowerCase() === "span") { + parent.insertBefore(newElementClone, upperParentStart.nextSibling); + parentStart.removeChild(parentStart.lastChild); + // Sometimes an empty previous sibling was created + if (newElementClone.previousSibling + && newElementClone.previousSibling.nodeType === 1 + && !newElementClone.previousSibling.innerText) { + parent.removeChild(newElementClone.previousSibling); + } + // The bookmark will not work anymore + newRange.moveToElementText(newElementClone); + newRange.collapse(false); + newRange.select(); + } else { + // Working around IE boookmark bug + if (parentStart != parentEnd) { + var newRange = editor._createRange(); + if (newRange.moveToBookmark(bookmark)) { + newRange.collapse(false); + newRange.select(); + } + } else { + range.collapse(false); + } + } + parent.normalize(); + } + } + } else { + // Add or remove class + if (parent && !HTMLArea.isBlockElement(parent)) { + if (className === "none" && parent.className && /\S/.test(parent.className)) { + classNames = parent.className.trim().split(" "); + HTMLArea._removeClass(parent, classNames[classNames.length-1]); + } + if (className !== "none") { + HTMLArea._addClass(parent, className); + } + // Remove the span tag if it has no more attribute + if ((parent.nodeName.toLowerCase() === "span") && !this.hasAllowedAttributes(parent)) { + this.removeMarkup(parent); + } + } + } + }, + + /* + * This function verifies if the element has any of the allowed attributes + */ + hasAllowedAttributes : function(element) { + var allowedAttributes = new Array("id", "title", "lang", "xml:lang", "dir", "class", "className"); + for (var i = 0; i < allowedAttributes.length; ++i) { + if (element.getAttribute(allowedAttributes[i])) { + return true; + } + } + return false; + }, + + /* + * This function removes the given markup element + */ + removeMarkup : function(element) { + var bookmark = this.editor.getBookmark(this.editor._createRange(this.editor._getSelection())); + var parent = element.parentNode; + while (element.firstChild) { + parent.insertBefore(element.firstChild, element); + } + parent.removeChild(element); + this.editor.selectRange(this.editor.moveToBookmark(bookmark)); + }, + + /* + * This function gets called when the plugin is generated + * Get the classes configuration and initiate the parsing of the style sheets + */ + onGenerate : function() { + this.generate(this.editor, "TextStyle"); + }, + + /* + * This function gets called on plugin generation, on toolbar update and on change mode + * Re-initiate the parsing of the style sheets, if not yet completed, and refresh our toolbar components + */ + generate : function(editor, dropDownId) { + if (this.cssLoaded) { + this.updateToolbar(dropDownId); + } else { + if (this.cssTimeout) { + if (editor._iframe.contentWindow) { + editor._iframe.contentWindow.clearTimeout(this.cssTimeout); + } else { + window.clearTimeout(this.cssTimeout); + } + this.cssTimeout = null; + } + if (this.classesUrl && (typeof(HTMLArea.classesLabels) === "undefined")) { + this.getJavascriptFile(this.classesUrl); + } + this.buildCssArray(editor, dropDownId); + } + }, + + buildCssArray : function(editor, dropDownId) { + this.cssArray = this.parseStyleSheet(); + if (!this.cssLoaded && (this.cssParseCount < 17)) { + var buildCssArrayLaterFunctRef = this.makeFunctionReference("buildCssArray"); + this.cssTimeout = editor._iframe.contentWindow ? editor._iframe.contentWindow.setTimeout(buildCssArrayLaterFunctRef, 200) : window.setTimeout(buildCssArrayLaterFunctRef, 200); + this.cssParseCount++; + } else { + this.cssTimeout = null; + this.cssLoaded = true; + this.cssArray = this.sortCssArray(this.cssArray); + this.updateToolbar(dropDownId); + } + }, + + parseStyleSheet : function() { + var iframe = this.editor._iframe.contentWindow ? this.editor._iframe.contentWindow.document : this.editor._iframe.contentDocument; + var newCssArray = new Object(); + this.cssLoaded = true; + for (var i = 0; i < iframe.styleSheets.length; i++) { + if (HTMLArea.is_gecko) { + try { + newCssArray = this.parseCssRule(iframe.styleSheets[i].cssRules, newCssArray); + } catch(e) { + this.cssLoaded = false; + } + } else { + try{ + // @import StyleSheets (IE) + if (iframe.styleSheets[i].imports) { + newCssArray = this.parseCssIEImport(iframe.styleSheets[i].imports, newCssArray); + } + if (iframe.styleSheets[i].rules) { + newCssArray = this.parseCssRule(iframe.styleSheets[i].rules, newCssArray); + } + } catch(e) { + this.cssLoaded = false; + } + } + } + return newCssArray; + }, + + parseCssIEImport : function(cssIEImport, cssArray) { + var newCssArray = new Object(); + newCssArray = cssArray; + for (var i=0; i < cssIEImport.length; i++) { + if (cssIEImport[i].imports) { + newCssArray = this.parseCssIEImport(cssIEImport[i].imports, newCssArray); + } + if (cssIEImport[i].rules) { + newCssArray = this.parseCssRule(cssIEImport[i].rules, newCssArray); + } + } + return newCssArray; + }, + + parseCssRule : function(cssRules, cssArray) { + var newCssArray = new Object(); + newCssArray = cssArray; + for (var rule = 0; rule < cssRules.length; rule++) { + // StyleRule + if (cssRules[rule].selectorText) { + newCssArray = this.parseSelectorText(cssRules[rule].selectorText, newCssArray); + } else { + // ImportRule (Mozilla) + if (cssRules[rule].styleSheet) { + newCssArray = this.parseCssRule(cssRules[rule].styleSheet.cssRules, newCssArray); + } + // MediaRule (Mozilla) + if (cssRules[rule].cssRules) { + newCssArray = this.parseCssRule(cssRules[rule].cssRules, newCssArray); + } + } + } + return newCssArray; + }, + + parseSelectorText : function(selectorText, cssArray) { + var cssElements = new Array(); + var cssElement = new Array(); + var tagName, className; + var newCssArray = new Object(); + newCssArray = cssArray; + if (selectorText.search(/:+/) == -1) { + // split equal Styles (Mozilla-specific) e.q. head, body {border:0px} + // for ie not relevant. returns allways one element + cssElements = selectorText.split(","); + for (var k = 0; k < cssElements.length; k++) { + cssElement = cssElements[k].split("."); + tagName = cssElement[0].toLowerCase().trim(); + if (!tagName) { + tagName = 'all'; + } + className = cssElement[1]; + if (!HTMLArea.reservedClassNames.test(className)) { + if (((tagName != "all") && (!this.tags || !this.tags[tagName])) + || ((tagName == "all") && (!this.tags || !this.tags[tagName]) && this.showTagFreeClasses) + || (this.tags && this.tags[tagName] && this.tags[tagName].allowedClasses.indexOf(className) != -1)) { + if (!newCssArray[tagName]) { + newCssArray[tagName] = new Object(); + } + if (className) { + cssName = className; + if (HTMLArea.classesLabels && HTMLArea.classesLabels[className]) { + cssName = this.prefixLabelWithClassName ? (className + " - " + HTMLArea.classesLabels[className]) : HTMLArea.classesLabels[className]; + cssName = this.postfixLabelWithClassName ? (cssName + " - " + className) : cssName; + } + } else { + className = 'none'; + cssName = this.localize("Element style"); + } + newCssArray[tagName][className] = cssName; + } + } + } + } + return newCssArray; + }, + + sortCssArray : function(cssArray) { + var newCssArray = new Object(); + for (var tagName in cssArray) { + if (cssArray.hasOwnProperty(tagName)) { + newCssArray[tagName] = new Object(); + var tagArrayKeys = new Array(); + for (var cssClass in cssArray[tagName]) { + if (cssArray[tagName].hasOwnProperty(cssClass)) { + tagArrayKeys.push(cssClass); + } + } + function compare(a, b) { + x = cssArray[tagName][a]; + y = cssArray[tagName][b]; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + } + tagArrayKeys = tagArrayKeys.sort(compare); + for (var i = 0; i < tagArrayKeys.length; ++i) { + newCssArray[tagName][tagArrayKeys[i]] = cssArray[tagName][tagArrayKeys[i]]; + } + } + } + return newCssArray; + }, + + /* + * This function gets called when the toolbar is being updated + */ + onUpdateToolbar : function() { + if (this.editor._editMode !== "textmode") { + this.generate(this.editor, "TextStyle"); + } + }, + + /* + * This function gets called when the drop-down list needs to be refreshed + */ + updateToolbar : function(dropDownId) { + var editor = this.editor; + if (editor._editMode !== "textmode") { + var tagName = false, classNames = Array(), fullNodeSelected = false; + var selection = editor._getSelection(); + var range = editor._createRange(selection); + var parent = editor.getParentElement(selection); + var ancestors = editor.getAllAncestors(); + if (parent && !HTMLArea.isBlockElement(parent)) { + tagName = parent.nodeName.toLowerCase(); + if (parent.className && /\S/.test(parent.className)) { + classNames = parent.className.trim().split(" "); + } + } + var selectionEmpty = editor._selectionEmpty(selection); + if (!selectionEmpty) { + for (var i = 0; i < ancestors.length; ++i) { + fullNodeSelected = (editor._statusBarTree.selected === ancestors[i]) + && ((HTMLArea.is_gecko && ancestors[i].textContent === range.toString()) || (HTMLArea.is_ie && ancestors[i].innerText === range.text)); + if (fullNodeSelected) { + if (!HTMLArea.isBlockElement(ancestors[i])) { + tagName = ancestors[i].nodeName.toLowerCase(); + if (ancestors[i].className && /\S/.test(ancestors[i].className)) { + classNames = ancestors[i].className.trim().split(" "); + } + } + break; + } + } + // Working around bug in Safari selectNodeContents + if (!fullNodeSelected && HTMLArea.is_safari && this.editor._statusBarTree.selected && this.isInlineElement(this.editor._statusBarTree.selected) && this.editor._statusBarTree.selected.textContent === range.toString()) { + fullNodeSelected = true; + tagName = this.editor._statusBarTree.selected.nodeName.toLowerCase(); + if (this.editor._statusBarTree.selected.className && /\S/.test(this.editor._statusBarTree.selected.className)) { + classNames = this.editor._statusBarTree.selected.className.trim().split(" "); + } + } + } + var selectionInInlineElement = tagName && this.REInlineTags.test(tagName); + var disabled = !this.endPointsInSameBlock() || (fullNodeSelected && !tagName) || (selectionEmpty && !selectionInInlineElement); + if (!disabled && !tagName) { + tagName = "span"; + } + + this.updateValue(dropDownId, tagName, classNames, selectionEmpty, fullNodeSelected, disabled); + } + }, + + /* + * This function determines if the end poins of the current selection are within the same block + */ + endPointsInSameBlock : function() { + var selection = this.editor._getSelection(); + if (this.editor._selectionEmpty(selection)) { + return true; + } else { + var parent = this.editor.getParentElement(selection); + var endBlocks = this.editor.getEndBlocks(selection); + return (endBlocks.start === endBlocks.end && !/^(body|table|thead|tbody|tfoot|tr)$/i.test(parent.nodeName)); + } + }, + + updateValue : function(dropDownId, tagName, classNames, selectionEmpty, fullNodeSelected, disabled) { + var editor = this.editor; + var select = document.getElementById(editor._toolbarObjects[dropDownId]["elementId"]); + var cssArray = new Array(); + + while(select.options.length > 0) { + select.options[select.length-1] = null; + } + select.options[0] = new Option(this.localize("No style"),"none"); + if (this.REInlineTags.test(tagName)) { + // Get classes allowed for all tags + if (typeof(this.cssArray["all"]) !== "undefined") { + if (this.tags && this.tags[tagName]) { + var allowedClasses = this.tags[tagName].allowedClasses; + for (cssClass in this.cssArray["all"]) { + if (allowedClasses.indexOf(cssClass) !== -1) { + cssArray[cssClass] = this.cssArray["all"][cssClass]; + } + } + } else { + for (cssClass in this.cssArray["all"]) { + if (this.cssArray["all"].hasOwnProperty(cssClass)) { + cssArray[cssClass] = this.cssArray["all"][cssClass]; + } + } + } + } + // Merge classes allowed for tagName and sort the array + if (typeof(this.cssArray[tagName]) !== "undefined") { + if (this.tags && this.tags[tagName]) { + var allowedClasses = this.tags[tagName].allowedClasses; + for (var cssClass in this.cssArray[tagName]) { + if (allowedClasses.indexOf(cssClass) !== -1) { + cssArray[cssClass] = this.cssArray[tagName][cssClass]; + } + } + } else { + for (var cssClass in this.cssArray[tagName]) { + if (this.cssArray[tagName].hasOwnProperty(cssClass)) { + cssArray[cssClass] = this.cssArray[tagName][cssClass]; + } + } + } + var sortedCssArray = new Object(); + var cssArrayKeys = new Array(); + for (var cssClass in cssArray) { + if (cssArray.hasOwnProperty(cssClass)) { + cssArrayKeys.push(cssClass); + } + } + function compare(a, b) { + x = cssArray[a]; + y = cssArray[b]; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + } + cssArrayKeys = cssArrayKeys.sort(compare); + for (var i = 0; i < cssArrayKeys.length; ++i) { + sortedCssArray[cssArrayKeys[i]] = cssArray[cssArrayKeys[i]]; + } + cssArray = sortedCssArray; + } + for (var cssClass in cssArray) { + if (cssArray.hasOwnProperty(cssClass) && cssArray[cssClass]) { + if (cssClass == "none") { + select.options[0] = new Option(cssArray[cssClass], cssClass); + } else { + select.options[select.options.length] = new Option(cssArray[cssClass], cssClass); + if (!editor.config.disablePCexamples && HTMLArea.classesValues && HTMLArea.classesValues[cssClass] && !HTMLArea.classesNoShow[cssClass]) { + select.options[select.options.length-1].setAttribute("style", HTMLArea.classesValues[cssClass]); + } + } + } + } + + select.selectedIndex = 0; + if (classNames.length && (selectionEmpty || fullNodeSelected)) { + for (i = select.options.length; --i >= 0;) { + if (classNames[classNames.length-1] == select.options[i].value) { + select.options[i].selected = true; + select.selectedIndex = i; + select.options[0].text = this.localize("Remove style"); + break; + } + } + if (select.selectedIndex == 0) { + select.options[select.options.length] = new Option(this.localize("Unknown style"), classNames[classNames.length-1]); + select.options[select.options.length-1].selected = true; + select.selectedIndex = select.options.length-1; + } + for (i = select.options.length; --i >= 0;) { + if (("," + classNames.join(",") + ",").indexOf("," + select.options[i].value + ",") !== -1) { + if (select.selectedIndex != i) { + select.options[i] = null; + } + } + } + } + } + select.disabled = !(select.options.length>1) || disabled; + select.className = ""; + if (select.disabled) { + select.className = "buttonDisabled"; + } + }, + + /* + * This function gets called when the editor has changed its mode to "wysiwyg" + */ + onMode : function(mode) { + if (mode === "wysiwyg") { + this.generate(this.editor, "TextStyle"); + } + } +}); + diff --git a/typo3/sysext/rtehtmlarea/res/proc/pageTSConfig.txt b/typo3/sysext/rtehtmlarea/res/proc/pageTSConfig.txt index 4880bdef0da9..a7c50fb2e8aa 100644 --- a/typo3/sysext/rtehtmlarea/res/proc/pageTSConfig.txt +++ b/typo3/sysext/rtehtmlarea/res/proc/pageTSConfig.txt @@ -55,6 +55,9 @@ RTE.default.proc { ## This is a list of additional attributes to keep keepPDIVattribs = id, title, dir, lang, xml:lang + ## DO NOT REMAP BOLD AND ITALIC TO STRONG AND EMPHASIS AND VICE VERSA + transformBoldAndItalicTags = 0 + ## CONTENT TO DATABASE entryHTMLparser_db = 1 entryHTMLparser_db { @@ -100,10 +103,12 @@ RTE.default.proc { q.allowedAttribs = id, title, dir, lang, xml:lang, class samp.allowedAttribs = id, title, dir, lang, xml:lang, class small.allowedAttribs = id, title, dir, lang, xml:lang, class + strike.allowedAttribs = id, title, dir, lang, xml:lang, class strong.allowedAttribs = id, title, dir, lang, xml:lang, class sub.allowedAttribs = id, title, dir, lang, xml:lang, class sup.allowedAttribs = id, title, dir, lang, xml:lang, class tt.allowedAttribs = id, title, dir, lang, xml:lang, class + u.allowedAttribs = id, title, dir, lang, xml:lang, class var.allowedAttribs = id, title, dir, lang, xml:lang, class } @@ -128,12 +133,6 @@ RTE.default.proc { exitHTMLparser_db = 1 exitHTMLparser_db { - ## REMAP B AND I TAGS - ## b and i tags are used by Mozilla/Firefox in editing mode. - ## This must be done on exit because the default HTMLparser_db parsing executes the reverse mapping. - tags.b.remap = strong - tags.i.remap = em - ## KEEP ALL TAGS ## Unwanted tags were removed on entry. ## Without this rule, the parser will remove all tags! Presumably, this rule will be more efficient than repeating the allowTags rule