當我從谷歌文檔複製文本,並粘貼到ckeditor使用粘貼從單詞按鈕它刪除所有的樣式(粗體,斜體,...)。
我該如何解決這個問題?ckeditor從谷歌文檔粘貼刪除樣式
重現步驟:
複製Google文檔中帶有下劃線和斜體的單詞將其粘貼到編輯器中。
預期結果:
該詞以斜體和下劃線表示。
實際結果:
該單詞是粗體,沒有下劃線或斜體被發現。
當我從谷歌文檔複製文本,並粘貼到ckeditor使用粘貼從單詞按鈕它刪除所有的樣式(粗體,斜體,...)。
我該如何解決這個問題?ckeditor從谷歌文檔粘貼刪除樣式
重現步驟:
複製Google文檔中帶有下劃線和斜體的單詞將其粘貼到編輯器中。
預期結果:
該詞以斜體和下劃線表示。
實際結果:
該單詞是粗體,沒有下劃線或斜體被發現。
我張貼我的情況下,它的解決方案可以幫助別人。 我從GoogleDocs粘貼時遇到了同樣的問題。 我發現有人在github(13877)中修改了CKEditor的一個分支,從而解決了這個問題。
因爲我需要它與我的4.5.6版本一起工作,所以我將它調整爲一個插件。 我已經完成了這項工作與Processwire,我不能改變CKEditor版本,並只在這種情況下進行了測試。
內容plugin.js的:
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
* 2017-07-05
* the code originally from Frederico Knabben, written in ckeditor-dev-t-13877 branch
* (https://github.com/cksource/ckeditor-dev/tree/t/13877)
* has been adapted in a plugin by Rpapier for use in Processwire. It hasn't been tested elsewhere.
* DESCRIPTION
* Filter to paste from Google Doc and keep style (bold, italic, underline)
* Those style must be present in the toolbar for it to show, otherwise, it will bypass it
* For processwire :
* you must edit the field that has CKEditor and make sure that :
* - ACF is On
* - pasteFromGoogleDoc plugin is enabled
* - CKEditor toolbar configuration contains Bold, Italic and Underline
* - e.g : Format, Styles, -, Bold, Italic, Underline, -, RemoveFormat
* if Underline is not in the toolbar for example, it will be bypassed by the filter.
*/
(function() {
'use strict';
CKEDITOR.plugins.add('pasteFromGoogleDoc', {
requires: ['clipboard'],
init: function(editor) {
// === arteractive hack for pasteFromGoogleDoc
var filterType,
filtersFactory = filtersFactoryFactory();
if (editor.config.forcePasteAsPlainText) {
filterType = 'plain-text';
} else if (editor.config.pasteFilter) {
filterType = editor.config.pasteFilter;
}
// On Webkit the pasteFilter defaults to 'webkit-default-filter' because pasted data is so terrible
// that it must be always filtered. (#13877)
else if (CKEDITOR.env.webkit && !('pasteFilter' in editor.config)) {
filterType = 'webkit-default-filter';
}
//console.log(filterType);
editor.pasteFilter = filtersFactory.get(filterType);
editor.on('paste', function(evt) {
// Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
if (!evt.data.dataTransfer) {
evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer();
}
// If dataValue is already set (manually or by paste bin), so do not override it.
if (evt.data.dataValue) {
return;
}
var dataTransfer = evt.data.dataTransfer,
// IE support only text data and throws exception if we try to get html data.
// This html data object may also be empty if we drag content of the textarea.
value = dataTransfer.getData('text/html');
if (value) {
evt.data.dataValue = value;
evt.data.type = 'html';
} else {
// Try to get text data otherwise.
value = dataTransfer.getData('text/plain');
if (value) {
evt.data.dataValue = editor.editable().transformPlainTextToHtml(value);
evt.data.type = 'text';
}
}
}, null, null, 1);
editor.on('paste', function(evt) {
var dataObj = evt.data,
type = dataObj.type,
data = dataObj.dataValue,
trueType,
// Default is 'html'.
defaultType = editor.config.clipboard_defaultContentType || 'html',
transferType = dataObj.dataTransfer.getTransferType(editor);
// If forced type is 'html' we don't need to know true data type.
if (type == 'html' || dataObj.preSniffing == 'html') {
trueType = 'html';
} else {
trueType = recogniseContentType(data);
}
// Unify text markup.
if (trueType == 'htmlifiedtext') {
data = htmlifiedTextHtmlification(editor.config, data);
}
// Strip presentational markup & unify text markup.
// Forced plain text (dialog or forcePAPT).
// Note: we do not check dontFilter option in this case, because forcePAPT was implemented
// before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
// forcePAPT should have priority as it had before 4.5.
if (type == 'text' && trueType == 'html') {
data = filterContent(editor, data, filtersFactory.get('plain-text'));
}
// External paste and pasteFilter exists and filtering isn't disabled.
else if (transferType == CKEDITOR.DATA_TRANSFER_EXTERNAL && editor.pasteFilter && !dataObj.dontFilter) {
// 2017-07-05 comment out this filter because it is already processsed somewhere...
//data = filterContent(editor, data, editor.pasteFilter);
//console.log(data);
}
if (dataObj.startsWithEOL) {
data = '<br data-cke-eol="1">' + data;
}
if (dataObj.endsWithEOL) {
data += '<br data-cke-eol="1">';
}
if (type == 'auto') {
type = (trueType == 'html' || defaultType == 'html') ? 'html' : 'text';
}
dataObj.type = type;
dataObj.dataValue = data;
delete dataObj.preSniffing;
delete dataObj.startsWithEOL;
delete dataObj.endsWithEOL;
/* evt.data.dataValue = data;
evt.data.dataValue = evt.data.dataValue
.replace(/zooterkins/gi, 'z********s')
.replace(/gadzooks/gi, 'g******s');*/
}, null, null, 6);
}
});
function filterContent(editor, data, filter) {
var fragment = CKEDITOR.htmlParser.fragment.fromHtml(data),
writer = new CKEDITOR.htmlParser.basicWriter();
filter.applyTo(fragment, true, false, editor.activeEnterMode);
fragment.writeHtml(writer);
return writer.getHtml();
}
// Returns:
// * 'htmlifiedtext' if content looks like transformed by browser from plain text.
// See clipboard/paste.html TCs for more info.
// * 'html' if it is not 'htmlifiedtext'.
function recogniseContentType(data) {
if (CKEDITOR.env.webkit) {
// Plain text or (<div><br></div> and text inside <div>).
if (!data.match(/^[^<]*$/g) && !data.match(/^(<div><br(?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi))
return 'html';
} else if (CKEDITOR.env.ie) {
// Text and <br> or (text and <br> in <p> - paragraphs can be separated by new \r\n).
if (!data.match(/^([^<]|<br(?\/)?>)*$/gi) && !data.match(/^(<p>([^<]|<br(?\/)?>)*<\/p>|(\r\n))*$/gi))
return 'html';
} else if (CKEDITOR.env.gecko) {
// Text or <br>.
if (!data.match(/^([^<]|<br(?\/)?>)*$/gi))
return 'html';
} else {
return 'html';
}
return 'htmlifiedtext';
}
// This function transforms what browsers produce when
// pasting plain text into editable element (see clipboard/paste.html TCs
// for more info) into correct HTML (similar to that produced by text2Html).
function htmlifiedTextHtmlification(config, data) {
function repeatParagraphs(repeats) {
// Repeat blocks floor((n+1)/2) times.
// Even number of repeats - add <br> at the beginning of last <p>.
return CKEDITOR.tools.repeat('</p><p>', ~~(repeats/2)) + (repeats % 2 == 1 ? '<br>' : '');
}
// Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space.
data = data.replace(/\s+/g, ' ')
// Remove spaces from between tags.
.replace(/> +</g, '><')
// Normalize XHTML syntax and upper cased <br> tags.
.replace(/<br ?\/>/gi, '<br>');
// IE - lower cased tags.
data = data.replace(/<\/?[A-Z]+>/g, function(match) {
return match.toLowerCase();
});
// Don't touch single lines (no <br|p|div>) - nothing to do here.
if (data.match(/^[^<]$/))
return data;
// Webkit.
if (CKEDITOR.env.webkit && data.indexOf('<div>') > -1) {
// One line break at the beginning - insert <br>
data = data.replace(/^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g, '<br>')
// Two or more - reduce number of new lines by one.
.replace(/^(<div>(<br>|)<\/div>){2}(?!$)/g, '<div></div>');
// Two line breaks create one paragraph in Webkit.
if (data.match(/<div>(<br>|)<\/div>/)) {
data = '<p>' + data.replace(/(<div>(<br>|)<\/div>)+/g, function(match) {
return repeatParagraphs(match.split('</div><div>').length + 1);
}) + '</p>';
}
// One line break create br.
data = data.replace(/<\/div><div>/g, '<br>');
// Remove remaining divs.
data = data.replace(/<\/?div>/g, '');
}
// Opera and Firefox and enterMode != BR.
if (CKEDITOR.env.gecko && config.enterMode != CKEDITOR.ENTER_BR) {
// Remove bogus <br> - Fx generates two <brs> for one line break.
// For two line breaks it still produces two <brs>, but it's better to ignore this case than the first one.
if (CKEDITOR.env.gecko)
data = data.replace(/^<br><br>$/, '<br>');
// This line satisfy edge case when for Opera we have two line breaks
//data = data.replace(/)
if (data.indexOf('<br><br>') > -1) {
// Two line breaks create one paragraph, three - 2, four - 3, etc.
data = '<p>' + data.replace(/(<br>){2,}/g, function(match) {
return repeatParagraphs(match.length/4);
}) + '</p>';
}
}
return switchEnterMode(config, data);
}
function filtersFactoryFactory() {
var filters = {};
// GDocs generates many spans and divs, therefore `all` parameter is used
// to create default filter in Webkit/Blink. (#13877)
function setUpTags(all) {
var tags = {};
for (var tag in CKEDITOR.dtd) {
if (tag.charAt(0) != '$' && (all || tag != 'div' && tag != 'span')) {
tags[ tag ] = 1;
}
}
return tags;
}
// Checks if content is pasted from Google Docs.
// Google Docs wraps everything in element with [id^=docs-internal-guid-],
// so that function just checks if such element exists. (#13877)
function isPastedFromGDocs(element) {
if (element.attributes.id && element.attributes.id.match(/^docs\-internal\-guid\-/)) {
return true;
} else if (element.parent && element.parent.name) {
return isPastedFromGDocs(element.parent);
}
return false;
}
// Process data from Google Docs:
// * turns `*[id^=docs-internal-guid-]` into `span`;
// * turns `span(text-decoration=underline)` into `u`;
// * turns `span(font-style=italic)` into `em`
// * turns `span(font-style=italic)(text-decoration=underline)` into `u > em`. (#13877)
//
function processDataFromGDocs(element) {
var styles = element.attributes.style && CKEDITOR.tools.parseCssText(element.attributes.style);
if (element.attributes.id && element.attributes.id.match(/^docs\-internal\-guid\-/)) {
return element.name = 'span';
}
if (!styles) {
return;
}
if (styles[ 'font-style' ] == 'italic' && styles[ 'text-decoration' ] == 'underline') {
element.name = 'em';
element.wrapWith(new CKEDITOR.htmlParser.element('u'));
if (styles[ 'font-weight' ] > 400) {
element.wrapWith(new CKEDITOR.htmlParser.element('strong'));
}
} else if (styles[ 'text-decoration' ] == 'underline') {
element.name = 'u';
if (styles[ 'font-weight' ] > 400) {
element.wrapWith(new CKEDITOR.htmlParser.element('strong'));
}
} else if (styles[ 'font-style' ] == 'italic') {
element.name = 'em';
if (styles[ 'font-weight' ] > 400) {
element.wrapWith(new CKEDITOR.htmlParser.element('strong'));
}
}
}
function createSemanticContentFilter() {
var filter = new CKEDITOR.filter();
filter.allow({
$1: {
elements: setUpTags(),
attributes: true,
styles: false,
classes: false
}
});
return filter;
}
function createWebkitDefaultFilter() {
var filter = createSemanticContentFilter();
// Preserves formatting while pasting from Google Docs in Webkit/Blink
// with default paste filter. (#13877)
filter.allow({
$2: {
elements: setUpTags(true),
attributes: true,
styles: true,
match: function(element) {
return isPastedFromGDocs(element);
}
}
});
filter.addElementCallback(processDataFromGDocs);
return filter;
}
return {
get: function(type) {
if (type == 'plain-text') {
// Does this look confusing to you? Did we forget about enter mode?
// It is a trick that let's us creating one filter for edidtor, regardless of its
// activeEnterMode (which as the name indicates can change during runtime).
//
// How does it work?
// The active enter mode is passed to the filter.applyTo method.
// The filter first marks all elements except <br> as disallowed and then tries to remove
// them. However, it cannot remove e.g. a <p> element completely, because it's a basic structural element,
// so it tries to replace it with an element created based on the active enter mode, eventually doing nothing.
//
// Now you can sleep well.
return filters.plainText || (filters.plainText = new CKEDITOR.filter('br'));
} else if (type == 'semantic-content') {
return filters.semanticContent || (filters.semanticContent = createSemanticContentFilter());
} else if (type == 'webkit-default-filter') {
// Webkit based browsers need semantic filter, because they produce terrible HTML without it.
// However original `'semantic-content'` filer is too strict and prevents pasting styled contents
// from many sources (e.g. Google Docs). Therefore that type extends original `'semantic-content'` filter. (#13877)
return filters.webkitDefaultFilter || (filters.webkitDefaultFilter = createWebkitDefaultFilter());
} else if (type) {
// Create filter based on rules (string or object).
return new CKEDITOR.filter(type);
}
return null;
}
};
}
function switchEnterMode(config, data) {
if (config.enterMode == CKEDITOR.ENTER_BR) {
data = data.replace(/(<\/p><p>)+/g, function(match) {
return CKEDITOR.tools.repeat('<br>', match.length/7 * 2);
}).replace(/<\/?p>/g, '');
} else if (config.enterMode == CKEDITOR.ENTER_DIV) {
data = data.replace(/<(\/)?p>/g, '<$1div>');
}
return data;
}
})();
它沒有工作。編輯我的文件:/docroot/sites/all/modules/ckeditor/ckeditor.config.js,並在文件末尾添加2行。 – mamesaye
如果你發佈你的config.js代碼,我們可以看看。 – Atzmon
這是配置文件:http://pastebin.com/7bwnzrx4。謝謝 – mamesaye