們看一下CKEditor4的編輯器內容的設置和獲取過程,也就是setData和getData過程。
我們在調用editor.setData的時候,調用的就是core/editor.js里面的setData方法。
// src/core/editor.js
setData: function( data, options, internal ) {
var fireSnapshot = true,
// Backward compatibility.
callback = options,
eventData;
if ( options && typeof options == 'object' ) {
internal = options.internal;
callback = options.callback;
fireSnapshot = !options.noSnapshot;
}
if ( !internal && fireSnapshot )
this.fire( 'saveSnapshot' );
if ( callback || !internal ) {
this.once( 'dataReady', function( evt ) {
if ( !internal && fireSnapshot )
this.fire( 'saveSnapshot' );
if ( callback )
callback.call( evt.editor );
} );
}
// Fire "setData" so data manipulation may happen.
eventData = { dataValue: data };
!internal && this.fire( 'setData', eventData );
this._.data = eventData.dataValue;
!internal && this.fire( 'afterSetData', eventData );
},
我們可以看到里面的set過程實際是分三步
saveSnapshot主要是方便撤銷操作的
// src/plugins/undo.plugin.js
// Save snapshots before doing custom changes.
editor.on( 'saveSnapshot', function( evt ) {
undoManager.save( evt.data && evt.data.contentOnly );
} );
我們接著看setData事件的處理
src/core/section.js
editor.on( 'setData', function() {
// Invalidate locked selection when unloading DOM.
// (https://dev.ckeditor.com/ticket/9521, https://dev.ckeditor.com/ticket/5217#comment:32 and https://dev.ckeditor.com/ticket/11500#comment:11)
editor.unlockSelection();
// Webkit's selection will mess up after the data loading.
if ( CKEDITOR.env.webkit )
clearSelection();
} );
我們可以看到,它做的工作主要是解鎖選區,看來實際做工作的還不是setData啊,它算是一個setData的準備工作可能更合適些。
// src/core/editable.js
this.attachListener( editor, 'afterSetData', function() {
this.setData( editor.getData( 1 ) );
}, this );
沒錯,這里又有個一個setData和getData。。。原來他們才是真正的setData和getData啊。
// src/core/editable.js
/**
* @see CKEDITOR.editor#setData
*/
setData: function( data, isSnapshot ) {
if ( !isSnapshot )
data = this.editor.dataProcessor.toHtml( data );
this.setHtml( data );
this.fixInitialSelection();
// Editable is ready after first setData.
if ( this.status == 'unloaded' )
this.status = 'ready';
this.editor.fire( 'dataReady' );
},
/**
* @see CKEDITOR.editor#getData
*/
getData: function( isSnapshot ) {
var data = this.getHtml();
if ( !isSnapshot )
data = this.editor.dataProcessor.toDataFormat( data );
return data;
},
setHtml和getHtml本質就是原生node的innerHTML,所以setData和getData的過程其實就是 this.editor.dataProcessor.toHtml和this.editor.dataProcessor.toDataFormat的過程,這個兩個方法哪來的?它們都源自dataProcessor,它是在編輯器初始化的時候賦值的。
// src/core/editor.js
// Various other core components that read editor configuration.
function initComponents( editor ) {
// Documented in dataprocessor.js.
editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor );
// Set activeFilter directly to avoid firing event.
editor.filter = editor.activeFilter = new CKEDITOR.filter( editor );
loadSkin( editor );
}
dataProcessor的兩個具體方法如下
// src/core/dataProcessor.js
toHtml: function( data, options, fixForBody, dontFilter ) {
var editor = this.editor,
context, filter, enterMode, protectedWhitespaces;
// Typeof null == 'object', so check truthiness of options too.
if ( options && typeof options == 'object' ) {
context = options.context;
fixForBody = options.fixForBody;
dontFilter = options.dontFilter;
filter = options.filter;
enterMode = options.enterMode;
protectedWhitespaces = options.protectedWhitespaces;
}
// Backward compatibility. Since CKEDITOR 4.3.0 every option was a separate argument.
else {
context = options;
}
// Fall back to the editable as context if not specified.
if ( !context && context !== null )
context = editor.editable().getName();
return editor.fire( 'toHtml', {
dataValue: data,
context: context,
fixForBody: fixForBody,
dontFilter: dontFilter,
filter: filter || editor.filter,
enterMode: enterMode || editor.enterMode,
protectedWhitespaces: protectedWhitespaces
} ).dataValue;
},
toDataFormat: function( html, options ) {
var context, filter, enterMode;
// Do not shorten this to `options && options.xxx`, because
// falsy `options` will be passed instead of undefined.
if ( options ) {
context = options.context;
filter = options.filter;
enterMode = options.enterMode;
}
// Fall back to the editable as context if not specified.
if ( !context && context !== null )
context = this.editor.editable().getName();
return this.editor.fire( 'toDataFormat', {
dataValue: html,
filter: filter || this.editor.filter,
context: context,
enterMode: enterMode || this.editor.enterMode
} ).dataValue;
},
這兩個方法的具體實現被化成對兩個(toHtml和toDataFormat)事件的處理邏輯了。
這兩個事件有哪些回調呢,先看toHtml。
// src/core/dataProcessor.js
editor.on( 'toHtml', function( evt ) {
var evtData = evt.data,
data = evtData.dataValue,
fixBodyTag;
// Before we start protecting markup, make sure there are no externally injected
// protection keywords.
data = removeReservedKeywords( data );
// The source data is already HTML, but we need to clean
// it up and apply the filter.
data = protectSource( data, editor );
// Protect content of textareas. (https://dev.ckeditor.com/ticket/9995)
// Do this before protecting attributes to avoid breaking:
// <textarea><img src="..." /></textarea>
data = protectElements( data, protectTextareaRegex );
// Before anything, we must protect the URL attributes as the
// browser may changing them when setting the innerHTML later in
// the code.
data = protectAttributes( data );
// Protect elements than can't be set inside a DIV. E.g. IE removes
// style tags from innerHTML. (https://dev.ckeditor.com/ticket/3710)
data = protectElements( data, protectElementsRegex );
// Certain elements has problem to go through DOM operation, protect
// them by prefixing 'cke' namespace. (https://dev.ckeditor.com/ticket/3591)
data = protectElementsNames( data );
// All none-IE browsers ignore self-closed custom elements,
// protecting them into open-close. (https://dev.ckeditor.com/ticket/3591)
data = protectSelfClosingElements( data );
// Compensate one leading line break after <pre> open as browsers
// eat it up. (https://dev.ckeditor.com/ticket/5789)
data = protectPreFormatted( data );
// There are attributes which may execute JavaScript code inside fixBin.
// Encode them greedily. They will be unprotected right after getting HTML from fixBin. (https://dev.ckeditor.com/ticket/10)
data = protectInsecureAttributes( data );
var fixBin = evtData.context || editor.editable().getName(),
isPre;
// Old IEs loose formats when load html into <pre>.
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && fixBin == 'pre' ) {
fixBin = 'div';
data = '<pre>' + data + '</pre>';
isPre = 1;
}
// Call the browser to help us fixing a possibly invalid HTML
// structure.
var el = editor.document.createElement( fixBin );
// Add fake character to workaround IE comments bug. (https://dev.ckeditor.com/ticket/3801)
el.setHtml( 'a' + data );
data = el.getHtml().substr( 1 );
// Restore shortly protected attribute names.
data = data.replace( new RegExp( 'data-cke-' + CKEDITOR.rnd + '-', 'ig' ), '' );
isPre && ( data = data.replace( /^<pre>|<\/pre>$/gi, '' ) );
// Unprotect "some" of the protected elements at this point.
data = unprotectElementNames( data );
data = unprotectElements( data );
// Restore the comments that have been protected, in this way they
// can be properly filtered.
data = unprotectRealComments( data );
if ( evtData.fixForBody === false ) {
fixBodyTag = false;
} else {
fixBodyTag = getFixBodyTag( evtData.enterMode, editor.config.autoParagraph );
}
// Now use our parser to make further fixes to the structure, as
// well as apply the filter.
data = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, fixBodyTag );
// The empty root element needs to be fixed by adding 'p' or 'div' into it.
// This avoids the need to create that element on the first focus (https://dev.ckeditor.com/ticket/12630).
if ( fixBodyTag ) {
fixEmptyRoot( data, fixBodyTag );
}
evtData.dataValue = data;
}, null, null, 5 );
// Filter incoming "data".
// Add element filter before htmlDataProcessor.dataFilter when purifying input data to correct html.
editor.on( 'toHtml', function( evt ) {
if ( evt.data.filter.applyTo( evt.data.dataValue, true, evt.data.dontFilter, evt.data.enterMode ) )
editor.fire( 'dataFiltered' );
}, null, null, 6 );
editor.on( 'toHtml', function( evt ) {
evt.data.dataValue.filterChildren( that.dataFilter, true );
}, null, null, 10 );
editor.on( 'toHtml', function( evt ) {
var evtData = evt.data,
data = evtData.dataValue,
writer = new CKEDITOR.htmlParser.basicWriter();
data.writeChildrenHtml( writer );
data = writer.getHtml( true );
// Protect the real comments again.
evtData.dataValue = protectRealComments( data );
}, null, null, 15 );
我們可以看到這些回調里面最多的幾個單詞就是protect和filter,它們主要也是做這些工作。
再看看toDataFormat的回調
// src/core/dataProcessor.js
editor.on( 'toDataFormat', function( evt ) {
var data = evt.data.dataValue;
// https://dev.ckeditor.com/ticket/10854 - we need to strip leading blockless <br> which FF adds
// automatically when editable contains only non-editable content.
// We do that for every browser (so it's a constant behavior) and
// not in BR mode, in which chance of valid leading blockless <br> is higher.
if ( evt.data.enterMode != CKEDITOR.ENTER_BR )
data = data.replace( /^<br *\/?>/i, '' );
evt.data.dataValue = CKEDITOR.htmlParser.fragment.fromHtml(
data, evt.data.context, getFixBodyTag( evt.data.enterMode, editor.config.autoParagraph ) );
}, null, null, 5 );
editor.on( 'toDataFormat', function( evt ) {
evt.data.dataValue.filterChildren( that.htmlFilter, true );
}, null, null, 10 );
// Transform outcoming "data".
// Add element filter after htmlDataProcessor.htmlFilter when preparing output data HTML.
editor.on( 'toDataFormat', function( evt ) {
evt.data.filter.applyTo( evt.data.dataValue, false, true );
}, null, null, 11 );
editor.on( 'toDataFormat', function( evt ) {
var data = evt.data.dataValue,
writer = that.writer;
writer.reset();
data.writeChildrenHtml( writer );
data = writer.getHtml( true );
// Restore those non-HTML protected source. (https://dev.ckeditor.com/ticket/4475,https://dev.ckeditor.com/ticket/4880)
data = unprotectRealComments( data );
data = unprotectSource( data, editor );
evt.data.dataValue = data;
}, null, null, 15 );
編輯器內容的設置和獲取表面上是簡單只是調用一個方法就完成了,但是其實內部的流程還是很長的,大致分為:
擊右上方,關注開源中國OSC頭條號,獲取最新技術資訊
CKEditor 5 v11.2.0 發布了,CKEditor 是一個網頁在線文字編輯器,特點是高性能與可擴展。
此版本帶來了期待已久的 Office 粘貼支持,例如可以直接復制 Microsoft Word 的文檔,還集成了 CKFinder 文件上傳器,此外,完善了圖像上傳文檔,改進了移動設備上的 UI,并引入了許多其它功能和改進。
復制 Word 文檔
此新功能允許粘貼 Microsoft Word 中的內容并保留原始結構和格式,包括基本文本樣式、標題級別、鏈接、列表、表格和圖像等。
集成 CKFinder
CKFinder 是一個文件管理器和上傳器,它是 CKEditor 4 的一個關鍵特性,現在已經集成到了此版本的 CKEditor 5 中。集成之后允許將圖像以及文件鏈接插入到編輯內容中。
其它變更
插件開發者需要關注一下,Position、Range 和 Writer 引擎類 API 已經發生變化。在此版本中,對 @ckeditor/ckeditor5-engine 軟件包中的公共 API 進行了一些重大更改,view 和 model 中不再開放 Position 和 Range 類的各種方法,例如Position#createAt()、Position#createFromParentAndOffset()、Range#createIn() 和Range#createCollapsedAt()。它們已被相應的方法替換,例如 createPositionAt( parent, offset )、createRangeIn() 和createRange( position )。
比如之前的寫法:
import Position from '@ckeditor/ckeditor5-engine/src/model/position'; import Range from '@ckeditor/ckeditor5-engine/src/model/range'; // ... editor.model.change( writer => { const imageElement = writer.createElement( 'image', { src: 'https://example.com/image.jpg' } ); // Insert the image at the current selection location. editor.model.insertContent( imageElement, editor.model.document.selection ); const paragraph = writer.createElement( 'paragraph' ); const insertPosition = Position.createAfter( imageElement ); writer.insert( paragraph, insertPosition ); // Set the selection in the <paragraph>. writer.setSelection( Range.createIn( paragraph ) ); } );
現在這樣寫:
// Imports from `@ckeditor/ckeditor5-engine` are no longer needed. // ... editor.model.change( writer => { const imageElement = writer.createElement( 'image', { src: 'https://example.com/image.jpg' } ); editor.model.insertContent( imageElement ); const paragraph = writer.createElement( 'paragraph' ); // Writer#createPositionAfter() instead of Position#createAfter(). const insertPosition = writer.createPositionAfter( imageElement ); writer.insert( paragraph, insertPosition ); // Writer#createRangeIn() instead of Range#createIn(). writer.setSelection( writer.createRangeIn( paragraph ) ); } );
移動設備上更好的 UI
改進的 UI 帶有下拉面板定位,可確保下拉內容始終對用戶可見。還使一些 UI 元素根據環境響應,例如,鏈接和圖像文本替代小氣球圖標現在適應屏幕的大小,使觸摸按鈕更容易。
其它更新內容,點擊下方“了解更多”,查看發布公告。
↓↓↓
KEditor 5 v23.1.0 穩定版已發布,主要更新內容包括:支持在編輯器嵌入原生 HTML 代碼并進行渲染、改進 reconversion API 以及支持將表格內容粘貼到另一個表格。
嵌入 Raw HTML
此功能支持在編輯器中嵌入任意 HTML 代碼片段,因此用戶可以嵌入其他 CKEditor 5 功能不支持的 HTML 代碼,并繞過編輯器的過濾機制。
因此該功能具有一定的風險,因為錯誤的配置可能會導致安全問題。在啟用 HTML 嵌入內容預覽功能前,請始終使用 sanitizer ,并使用 CSP 額外保護網站。
詳細用法查看使用文檔。
將表格內容粘貼到另一個表格
支持多種情況:
其他改進和 bugfix
*請認真填寫需求信息,我們會在24小時內與您取得聯系。