diff --git a/CHANGES.md b/CHANGES.md index 31084ed0019..b208bbf9247 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ Fixed Issues: * [#10866](http://dev.ckeditor.com/ticket/10866): Fixed: Broken *Tab* key navigation in the Image2 dialog. * [#10854](http://dev.ckeditor.com/ticket/10854): Fixed: Firefox prepends `
` to ``, so it is stripped by the HTML data processor. * [#10823](http://dev.ckeditor.com/ticket/10823): Fixed: Link plugin does not work with non-editable content. +* [#10828](http://dev.ckeditor.com/ticket/10828): Magicline integration with widgets system. ## CKEditor 4.3 Beta diff --git a/plugins/magicline/plugin.js b/plugins/magicline/plugin.js index 85eee75135d..8e01ca0aa08 100644 --- a/plugins/magicline/plugin.js +++ b/plugins/magicline/plugin.js @@ -17,12 +17,6 @@ // Activates the box inside of an editor. function initPlugin( editor ) { - - var enterBehaviors = {}; - enterBehaviors[ CKEDITOR.ENTER_BR ] = 'br'; - enterBehaviors[ CKEDITOR.ENTER_P ] = 'p'; - enterBehaviors[ CKEDITOR.ENTER_DIV ] = 'div'; - // Configurables var config = editor.config, triggerOffset = config.magicline_triggerOffset || 30, @@ -30,13 +24,12 @@ that = { // Global stuff is being initialized here. editor: editor, - enterBehavior: enterBehaviors[ enterMode ], // A tag which is to be inserted by the magicline. enterMode: enterMode, triggerOffset: triggerOffset, holdDistance: 0 | triggerOffset * ( config.magicline_holdDistance || 0.5 ), boxColor: config.magicline_color || '#ff0000', rtl: config.contentsLangDirection == 'rtl', - tabuList: [ 'data-widget-wrapper' ].concat( config.magicline_tabuList || [] ), + tabuList: [ 'data-cke-hidden-sel' ].concat( config.magicline_tabuList || [] ), triggers: config.magicline_everywhere ? DTD_BLOCK : { table:1,hr:1,div:1,ul:1,ol:1,dl:1,form:1,blockquote:1 } }, scrollTimeout, checkMouseTimeoutPending, checkMouseTimeout, checkMouseTimer; @@ -86,7 +79,8 @@ editable: editable, inInlineMode: editable.isInline(), doc: doc, - win: win + win: win, + hotNode: null }, true ); // This is the boundary of the editor. For inline the boundary is editable itself. @@ -278,15 +272,15 @@ // Revert magicline hot node on undo/redo. editor.on( 'loadSnapshot', function( event ) { - var elements = editor.document.getElementsByTag( that.enterBehavior ), + var elements = doc.find( 'p,br,div' ), element; for ( var i = elements.count(); i--; ) { - if ( ( element = elements.getItem( i ) ).hasAttribute( 'data-cke-magicline-hot' ) ) { + if ( ( element = elements.getItem( i ) ).data( 'cke-magicline-hot' ) ) { // Restore hotNode that.hotNode = element; // Restore last access direction - that.lastCmdDirection = element.getAttribute( 'data-cke-magicline-dir' ) === 'true' ? true : false; + that.lastCmdDirection = element.data( 'cke-magicline-dir' ) === 'true' ? true : false; break; } } @@ -359,6 +353,9 @@ env = CKEDITOR.env, dtd = CKEDITOR.dtd, + // Global object associating enter modes with elements. + enterElements = {}, + // Constant values, types and so on. EDGE_TOP = 128, EDGE_BOTTOM = 64, @@ -383,6 +380,10 @@ CSS_TRIANGLE = CSS_COMMON + 'border-color:transparent;display:block;border-style:solid;', TRIANGLE_HTML = '' + WHITE_SPACE + ''; + enterElements[ CKEDITOR.ENTER_BR ] = 'br'; + enterElements[ CKEDITOR.ENTER_P ] = 'p'; + enterElements[ CKEDITOR.ENTER_DIV ] = 'div'; + function areSiblings( that, upper, lower ) { return isHtml( upper ) && isHtml( lower ) && lower.equals( upper.getNext( function( node ) { return !( isEmptyTextNode( node ) || isComment( node ) || isFlowBreaker( node ) ); @@ -462,9 +463,25 @@ trigger; if ( node && isHtml( node ) ) { - return ( trigger = node.getAscendant( that.triggers, true ) ) && - !trigger.contains( that.editable ) && - !trigger.equals( that.editable ) ? trigger : null; + trigger = node.getAscendant( that.triggers, true ); + + // If trigger is an element, neither editable nor editable's ascendant. + if ( trigger && !trigger.contains( that.editable ) && !trigger.equals( that.editable ) ) { + // Check for closest editable limit. + var limit = getClosestEditableLimit( trigger, true ); + + // Trigger in nested editable area. + if ( limit.getAttribute( 'contenteditable' ) == 'true' ) + return trigger + // Trigger in non-editable area. + else if ( limit.is( that.triggers ) ) + return limit; + else + return null; + + return trigger; + } else + return null; } return null; @@ -497,6 +514,29 @@ return val > lower && val < upper; } + // Returns the closest ancestor that has contenteditable attribute. + // Such ancestor is the limit of (non-)editable DOM branch that element + // belongs to. This method omits editor editable. + function getClosestEditableLimit( element, includeSelf ) { + if ( element.data( 'cke-editable' ) ) + return null; + + if ( !includeSelf ) + element = element.getParent(); + + while ( element ) { + if ( element.data( 'cke-editable' ) ) + return null; + + if ( element.hasAttribute( 'contenteditable' ) ) + return element; + + element = element.getParent(); + } + + return null; + } + // Access space line consists of a few elements (spans): // \-> Line wrapper. // \-> Line. @@ -759,9 +799,19 @@ // In other cases a regular element is used. else { - accessNode = new newElement( that.enterBehavior, that.doc ); + // Use the enterMode of editable's limit or editor's + // enter mode if not in nested editable. + var limit = getClosestEditableLimit( that.element, true ), - if ( that.enterMode != CKEDITOR.ENTER_BR ) { + // This is an enter mode for the context. We cannot use + // editor.activeEnterMode because the focused nested editable will + // have a different enterMode as editor but magicline will be inserted + // directly into editor's editable. + enterMode = limit && limit.data( 'cke-enter-mode' ) || that.enterMode; + + accessNode = new newElement( enterElements[ enterMode ], that.doc ); + + if ( !accessNode.is( 'br' ) ) { var dummy = that.doc.createText( WHITE_SPACE ); dummy.appendTo( accessNode ); } @@ -857,7 +907,8 @@ } return function( editor ) { - var selected = editor.getSelection().getStartElement(); + var selected = editor.getSelection().getStartElement(), + limit; // (#9833) Go down to the closest non-inline element in DOM structure // since inline elements don't participate in in magicline. @@ -873,6 +924,11 @@ if ( !selected || selected.equals( that.editable ) || selected.contains( that.editable ) ) return; + // Executing the command directly in nested editable should + // access space before/after it. + if ( ( limit = getClosestEditableLimit( selected ) ) && limit.getAttribute( 'contenteditable' ) == 'false' ) + selected = limit; + // That holds element from mouse. Replace it with the // element under the caret. that.element = selected; @@ -969,7 +1025,7 @@ var isComment = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_COMMENT ); function isPositioned( element ) { - return !!{ absolute:1,fixed:1,relative:1 }[ element.getComputedStyle( 'position' ) ]; + return !!{ absolute:1,fixed:1 }[ element.getComputedStyle( 'position' ) ]; } // Is text node? @@ -1058,7 +1114,7 @@ // Edge node according to bottomTrigger. edgeNode = editable[ bottomTrigger ? 'getLast' : 'getFirst' ]( function( node ) { return !( isEmptyTextNode( node ) || isComment( node ) ); - }); + } ); // There's no edge node. Abort. if ( !edgeNode ) { @@ -1331,6 +1387,10 @@ return null; } + // Stop searching if element is in non-editable branch of DOM. + if ( startElement.isReadOnly() ) + return null; + trigger = verticalSearch( that, function( current, startElement ) { return !startElement.equals( current ); // stop when start element and the current one differ