mirror of
https://github.com/processwire/processwire.git
synced 2025-08-11 09:14:58 +02:00
InputfieldTinyMCE: Add support for new "remove all style attributes" option to the Markup Toggle settings. Plus refactoring of the pasteFilter JS in attempt to fix processwire/processwire-issues#1866 which should improve pasting from MS Word.
This commit is contained in:
@@ -598,7 +598,7 @@ var InputfieldTinyMCE = {
|
|||||||
|
|
||||||
var t = InputfieldTinyMCE;
|
var t = InputfieldTinyMCE;
|
||||||
var allow = ',' + ProcessWire.config.InputfieldTinyMCE.pasteFilter + ',';
|
var allow = ',' + ProcessWire.config.InputfieldTinyMCE.pasteFilter + ',';
|
||||||
var regexTag = /<([a-z0-9]+)([^>]*)>/gi;
|
var regexTag = /<([a-z0-9:!\[\]]+)([^>]*)>/gi;
|
||||||
var regexAttr = /([-_a-z0-9]+)=["']([^"']*)["']/gi;
|
var regexAttr = /([-_a-z0-9]+)=["']([^"']*)["']/gi;
|
||||||
var html = args.content;
|
var html = args.content;
|
||||||
var matchTag, matchAttr;
|
var matchTag, matchAttr;
|
||||||
@@ -607,13 +607,30 @@ var InputfieldTinyMCE = {
|
|||||||
var replaces = [];
|
var replaces = [];
|
||||||
var startLength = html.length;
|
var startLength = html.length;
|
||||||
|
|
||||||
|
allow = allow.toLowerCase();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HTML for testing MS word paste
|
||||||
|
msWordHtml =
|
||||||
|
"<p className=MsoNormal>This is <b>bold</b> text. <o:p></o:p></p>\n\n" +
|
||||||
|
"<h2>This is headline 2. <o:p></o:p></h2>\n\n" +
|
||||||
|
"<p className=MsoNormal>This is <I>italic</I> text<o:p></o:p></p>\n\n" +
|
||||||
|
"<p className=MsoListParagraphCxSpFirst style='text-indent:-.25in;mso-list:10 level1 lfo1'>" +
|
||||||
|
"<![if !supportsLists]><span style='iso-bidi-font-family:Aptos;mso-bidi-theme-font:minor-latin'>" +
|
||||||
|
"<span style='so-list:Ignore'>1.<span style='font:7.0pt \"Times New Roman\"'>" +
|
||||||
|
" </span></span></span><![endif]>One <o:p></o:p></p>";
|
||||||
|
html = msWordHtml;
|
||||||
|
*/
|
||||||
|
|
||||||
|
startLength = html.length;
|
||||||
|
|
||||||
if(args.internal) {
|
if(args.internal) {
|
||||||
t.log('Skipping pasteFilter for interal copy/paste');
|
t.log('Skipping pasteFilter for interal copy/paste');
|
||||||
return; // skip filtering for internal copy/paste operations
|
return; // skip filtering for internal copy/paste operations
|
||||||
}
|
}
|
||||||
|
|
||||||
if(allow === ',text,') {
|
if(allow === ',text,') {
|
||||||
t.log('Skipping pasteFilter since paste_as_text settingw will be used');
|
t.log('Skipping pasteFilter since paste_as_text settings will be used');
|
||||||
return; // will be processed by paste_as_text setting
|
return; // will be processed by paste_as_text setting
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,17 +638,19 @@ var InputfieldTinyMCE = {
|
|||||||
|
|
||||||
var tagOpen = matchTag[0]; // i.e. <strong>, <img src="..">, <h2>, etc.
|
var tagOpen = matchTag[0]; // i.e. <strong>, <img src="..">, <h2>, etc.
|
||||||
var tagName = matchTag[1]; // i.e. 'strong', 'img', 'h2', etc.
|
var tagName = matchTag[1]; // i.e. 'strong', 'img', 'h2', etc.
|
||||||
|
var tagNameLower = tagName.toLowerCase();
|
||||||
var tagClose = '</' + tagName + '>'; // i.e. </strong>, </h2>
|
var tagClose = '</' + tagName + '>'; // i.e. </strong>, </h2>
|
||||||
var tagAttrs = matchTag[2]; // i.e. 'src="a.jpg" alt="alt"'
|
var tagAttrs = matchTag[2]; // i.e. 'src="a.jpg" alt="alt"'
|
||||||
var allowAttrs = false;
|
var allowAttrs = false;
|
||||||
|
|
||||||
// first see if we can match a tag replacement
|
// first see if we can match a tag replacement
|
||||||
var find = ',' + tagName + '='; // i.e. ',b=strong'
|
var findTagEqual = ',' + tagNameLower + '='; // i.e. ',b=strong'
|
||||||
var pos = allow.indexOf(find);
|
var findTagAttr = ',' + tagNameLower + '['; // i.e. ',b[...]'
|
||||||
|
var findTagOnly = ',' + tagNameLower + ','; // i.e. ,b
|
||||||
|
var posTagEqual = allow.indexOf(findTagEqual);
|
||||||
|
|
||||||
if(pos > -1) {
|
if(posTagEqual > -1) {
|
||||||
// tag replacement
|
var rule = allow.substring(posTagEqual + 1); // i.e. b=strong,and,more
|
||||||
var rule = allow.substring(pos + 1); // i.e. b=strong,and,more
|
|
||||||
rule = rule.substring(0, rule.indexOf(',')); // i.e. b=strong
|
rule = rule.substring(0, rule.indexOf(',')); // i.e. b=strong
|
||||||
rule = rule.split('=');
|
rule = rule.split('=');
|
||||||
var replaceTag = rule[1];
|
var replaceTag = rule[1];
|
||||||
@@ -641,11 +660,11 @@ var InputfieldTinyMCE = {
|
|||||||
replaces.push('</' + replaceTag + '>');
|
replaces.push('</' + replaceTag + '>');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(allow.indexOf(',' + tagName + '[') > -1) {
|
if(allow.indexOf(findTagAttr) > -1) {
|
||||||
// tag appears in whitelist with attributes
|
// tag appears in whitelist with attributes
|
||||||
allowAttrs = true;
|
allowAttrs = true;
|
||||||
} else if(allow.indexOf(',' + tagName + ',') === -1) {
|
} else if(posTagEqual === -1 && allow.indexOf(findTagOnly) === -1) {
|
||||||
// tag does not appear in whitelist
|
// tag does not appear in whitelist at all
|
||||||
removals.push(tagOpen);
|
removals.push(tagOpen);
|
||||||
removals.push(tagClose);
|
removals.push(tagClose);
|
||||||
continue;
|
continue;
|
||||||
|
@@ -74,7 +74,7 @@ class InputfieldTinyMCE extends InputfieldTextarea implements ConfigurableModule
|
|||||||
return array(
|
return array(
|
||||||
'title' => 'TinyMCE',
|
'title' => 'TinyMCE',
|
||||||
'summary' => 'TinyMCE rich text editor version ' . self::mceVersion . '.',
|
'summary' => 'TinyMCE rich text editor version ' . self::mceVersion . '.',
|
||||||
'version' => 617,
|
'version' => 618,
|
||||||
'icon' => 'keyboard-o',
|
'icon' => 'keyboard-o',
|
||||||
'requires' => 'ProcessWire>=3.0.200, MarkupHTMLPurifier',
|
'requires' => 'ProcessWire>=3.0.200, MarkupHTMLPurifier',
|
||||||
);
|
);
|
||||||
@@ -89,6 +89,7 @@ class InputfieldTinyMCE extends InputfieldTextarea implements ConfigurableModule
|
|||||||
const toggleCleanDiv = 2; // remove <div>s
|
const toggleCleanDiv = 2; // remove <div>s
|
||||||
const toggleCleanP = 4; // remove empty <p> tags
|
const toggleCleanP = 4; // remove empty <p> tags
|
||||||
const toggleCleanNbsp = 8; // remove entities
|
const toggleCleanNbsp = 8; // remove entities
|
||||||
|
const toggleRemoveStyles = 16; // remove all style attributes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default configuration for filtered paste
|
* Default configuration for filtered paste
|
||||||
|
@@ -711,6 +711,7 @@ class InputfieldTinyMCEConfigs extends InputfieldTinyMCEClass {
|
|||||||
$f->addOption(InputfieldTinyMCE::toggleCleanDiv, $this->_('Convert `<div>` tags to `<p>` tags on save?'));
|
$f->addOption(InputfieldTinyMCE::toggleCleanDiv, $this->_('Convert `<div>` tags to `<p>` tags on save?'));
|
||||||
$f->addOption(InputfieldTinyMCE::toggleCleanP, $this->_('Remove empty `<p>` tags on save?'));
|
$f->addOption(InputfieldTinyMCE::toggleCleanP, $this->_('Remove empty `<p>` tags on save?'));
|
||||||
$f->addOption(InputfieldTinyMCE::toggleCleanNbsp, $this->_('Remove non-breaking spaces on save?'));
|
$f->addOption(InputfieldTinyMCE::toggleCleanNbsp, $this->_('Remove non-breaking spaces on save?'));
|
||||||
|
$f->addOption(InputfieldTinyMCE::toggleRemoveStyles, $this->_('Remove `style` attributes from all elements'));
|
||||||
$f->attr('value', $this->inputfield->toggles);
|
$f->attr('value', $this->inputfield->toggles);
|
||||||
$f->collapsed = Inputfield::collapsedYes;
|
$f->collapsed = Inputfield::collapsedYes;
|
||||||
$f->themeOffset = 1;
|
$f->themeOffset = 1;
|
||||||
|
@@ -163,22 +163,38 @@ class InputfieldTinyMCETools extends InputfieldTinyMCEClass {
|
|||||||
$toggles = $this->inputfield->toggles;
|
$toggles = $this->inputfield->toggles;
|
||||||
if(!is_array($toggles)) return $value;
|
if(!is_array($toggles)) return $value;
|
||||||
|
|
||||||
if(in_array(InputfieldTinyMCE::toggleCleanDiv, $toggles) && strpos($value, '<div') !== false) {
|
foreach($toggles as $toggle) {
|
||||||
|
switch($toggle) {
|
||||||
|
case InputfieldTinyMCE::toggleCleanDiv:
|
||||||
|
// convert <div> to <p>
|
||||||
|
if(strpos($value, '<div') !== false) {
|
||||||
$value = preg_replace('{\s*(</?)div[^><]*>\s*}is', '$1' . 'p>', $value);
|
$value = preg_replace('{\s*(</?)div[^><]*>\s*}is', '$1' . 'p>', $value);
|
||||||
while(strpos($value, '<p><p>') !== false) {
|
while(strpos($value, '<p><p>') !== false) {
|
||||||
$value = str_replace(array('<p><p>', '</p></p>'), array('<p>', '</p>'), $value);
|
$value = str_replace(array('<p><p>', '</p></p>'), array('<p>', '</p>'), $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
// remove gratuitous whitespace
|
case InputfieldTinyMCE::toggleCleanP:
|
||||||
if(in_array(InputfieldTinyMCE::toggleCleanP, $toggles)) {
|
// remove empty paragraphs
|
||||||
$value = str_replace(array('<p><br /></p>', '<p> </p>', "<p>\xc2\xa0</p>", '<p></p>', '<p> </p>'), '', $value);
|
$value = str_replace(array('<p><br /></p>', '<p> </p>', "<p>\xc2\xa0</p>", '<p></p>', '<p> </p>'), '', $value);
|
||||||
}
|
break;
|
||||||
|
case InputfieldTinyMCE::toggleCleanNbsp:
|
||||||
// convert non-breaking space to regular space
|
// convert non-breaking space to regular space
|
||||||
if(in_array(InputfieldTinyMCE::toggleCleanNbsp, $toggles)) {
|
|
||||||
$value = str_ireplace(' ', ' ', $value);
|
$value = str_ireplace(' ', ' ', $value);
|
||||||
$value = str_replace("\xc2\xa0",' ', $value);
|
$value = str_replace("\xc2\xa0",' ', $value);
|
||||||
|
break;
|
||||||
|
case InputfieldTinyMCE::toggleRemoveStyles:
|
||||||
|
// remove all style attributes
|
||||||
|
if(strpos($value, 'style=')) {
|
||||||
|
if(preg_match_all('!(<.+?)\sstyle=(["\']).*?\2!i', $value, $matches)) {
|
||||||
|
foreach($matches[0] as $key => $fullMatch) {
|
||||||
|
$startMatch = $matches[1][$key];
|
||||||
|
$value = str_replace($fullMatch, $startMatch, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
Reference in New Issue
Block a user