aboutsummaryrefslogtreecommitdiff
path: root/sources/plugins/link/plugin.js
diff options
context:
space:
mode:
Diffstat (limited to 'sources/plugins/link/plugin.js')
-rw-r--r--sources/plugins/link/plugin.js133
1 files changed, 111 insertions, 22 deletions
diff --git a/sources/plugins/link/plugin.js b/sources/plugins/link/plugin.js
index db11376..120097f 100644
--- a/sources/plugins/link/plugin.js
+++ b/sources/plugins/link/plugin.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -9,7 +9,7 @@
9 CKEDITOR.plugins.add( 'link', { 9 CKEDITOR.plugins.add( 'link', {
10 requires: 'dialog,fakeobjects', 10 requires: 'dialog,fakeobjects',
11 // jscs:disable maximumLineLength 11 // jscs:disable maximumLineLength
12 lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE% 12 lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
13 // jscs:enable maximumLineLength 13 // jscs:enable maximumLineLength
14 icons: 'anchor,anchor-rtl,link,unlink', // %REMOVE_LINE_CORE% 14 icons: 'anchor,anchor-rtl,link,unlink', // %REMOVE_LINE_CORE%
15 hidpi: true, // %REMOVE_LINE_CORE% 15 hidpi: true, // %REMOVE_LINE_CORE%
@@ -52,7 +52,7 @@
52 required = 'a[href]'; 52 required = 'a[href]';
53 53
54 if ( CKEDITOR.dialog.isTabEnabled( editor, 'link', 'advanced' ) ) 54 if ( CKEDITOR.dialog.isTabEnabled( editor, 'link', 'advanced' ) )
55 allowed = allowed.replace( ']', ',accesskey,charset,dir,id,lang,name,rel,tabindex,title,type]{*}(*)' ); 55 allowed = allowed.replace( ']', ',accesskey,charset,dir,id,lang,name,rel,tabindex,title,type,download]{*}(*)' );
56 if ( CKEDITOR.dialog.isTabEnabled( editor, 'link', 'target' ) ) 56 if ( CKEDITOR.dialog.isTabEnabled( editor, 'link', 'target' ) )
57 allowed = allowed.replace( ']', ',target,onclick]' ); 57 allowed = allowed.replace( ']', ',target,onclick]' );
58 58
@@ -92,9 +92,12 @@
92 CKEDITOR.dialog.add( 'anchor', this.path + 'dialogs/anchor.js' ); 92 CKEDITOR.dialog.add( 'anchor', this.path + 'dialogs/anchor.js' );
93 93
94 editor.on( 'doubleclick', function( evt ) { 94 editor.on( 'doubleclick', function( evt ) {
95 var element = CKEDITOR.plugins.link.getSelectedLink( editor ) || evt.data.element; 95 // If the link has descendants and the last part of it is also a part of a word partially
96 // unlinked, clicked element may be a descendant of the link, not the link itself (http://dev.ckeditor.com/ticket/11956).
97 // The evt.data.element.getAscendant( 'img', 1 ) condition allows opening anchor dialog if the anchor is empty (#501).
98 var element = evt.data.element.getAscendant( { a: 1, img: 1 }, true );
96 99
97 if ( !element.isReadOnly() ) { 100 if ( element && !element.isReadOnly() ) {
98 if ( element.is( 'a' ) ) { 101 if ( element.is( 'a' ) ) {
99 evt.data.dialog = ( element.getAttribute( 'name' ) && ( !element.getAttribute( 'href' ) || !element.getChildCount() ) ) ? 'anchor' : 'link'; 102 evt.data.dialog = ( element.getAttribute( 'name' ) && ( !element.getAttribute( 'href' ) || !element.getChildCount() ) ) ? 'anchor' : 'link';
100 103
@@ -108,7 +111,7 @@
108 111
109 // If event was cancelled, link passed in event data will not be selected. 112 // If event was cancelled, link passed in event data will not be selected.
110 editor.on( 'doubleclick', function( evt ) { 113 editor.on( 'doubleclick', function( evt ) {
111 // Make sure both links and anchors are selected (#11822). 114 // Make sure both links and anchors are selected (http://dev.ckeditor.com/ticket/11822).
112 if ( evt.data.dialog in { link: 1, anchor: 1 } && evt.data.link ) 115 if ( evt.data.dialog in { link: 1, anchor: 1 } && evt.data.link )
113 editor.getSelection().selectElement( evt.data.link ); 116 editor.getSelection().selectElement( evt.data.link );
114 }, null, null, 20 ); 117 }, null, null, 20 );
@@ -311,20 +314,38 @@
311 * 314 *
312 * @since 3.2.1 315 * @since 3.2.1
313 * @param {CKEDITOR.editor} editor 316 * @param {CKEDITOR.editor} editor
317 * @param {Boolean} [returnMultiple=false] Indicates whether the function should return only the first selected link or all of them.
318 * @returns {CKEDITOR.dom.element/CKEDITOR.dom.element[]/null} A single link element or an array of link
319 * elements relevant to the current selection.
314 */ 320 */
315 getSelectedLink: function( editor ) { 321 getSelectedLink: function( editor, returnMultiple ) {
316 var selection = editor.getSelection(); 322 var selection = editor.getSelection(),
317 var selectedElement = selection.getSelectedElement(); 323 selectedElement = selection.getSelectedElement(),
318 if ( selectedElement && selectedElement.is( 'a' ) ) 324 ranges = selection.getRanges(),
325 links = [],
326 link,
327 range,
328 i;
329
330 if ( !returnMultiple && selectedElement && selectedElement.is( 'a' ) ) {
319 return selectedElement; 331 return selectedElement;
332 }
333
334 for ( i = 0; i < ranges.length; i++ ) {
335 range = selection.getRanges()[ i ];
320 336
321 var range = selection.getRanges()[ 0 ]; 337 // Skip bogus to cover cases of multiple selection inside tables (#tp2245).
338 range.shrink( CKEDITOR.SHRINK_TEXT, false, { skipBogus: true } );
339 link = editor.elementPath( range.getCommonAncestor() ).contains( 'a', 1 );
322 340
323 if ( range ) { 341 if ( link && returnMultiple ) {
324 range.shrink( CKEDITOR.SHRINK_TEXT ); 342 links.push( link );
325 return editor.elementPath( range.getCommonAncestor() ).contains( 'a', 1 ); 343 } else if ( link ) {
344 return link;
345 }
326 } 346 }
327 return null; 347
348 return returnMultiple ? links : null;
328 }, 349 },
329 350
330 /** 351 /**
@@ -340,7 +361,7 @@
340 var editable = editor.editable(), 361 var editable = editor.editable(),
341 362
342 // The scope of search for anchors is the entire document for inline editors 363 // The scope of search for anchors is the entire document for inline editors
343 // and editor's editable for classic editor/divarea (#11359). 364 // and editor's editable for classic editor/divarea (http://dev.ckeditor.com/ticket/11359).
344 scope = ( editable.isInline() && !editor.plugins.divarea ) ? editor.document : editable, 365 scope = ( editable.isInline() && !editor.plugins.divarea ) ? editor.document : editable,
345 366
346 links = scope.getElementsByTag( 'a' ), 367 links = scope.getElementsByTag( 'a' ),
@@ -384,7 +405,7 @@
384 fakeAnchor: true, 405 fakeAnchor: true,
385 406
386 /** 407 /**
387 * For browsers that do not support CSS3 `a[name]:empty()`. Note that IE9 is included because of #7783. 408 * For browsers that do not support CSS3 `a[name]:empty()`. Note that IE9 is included because of http://dev.ckeditor.com/ticket/7783.
388 * 409 *
389 * @readonly 410 * @readonly
390 * @deprecated 4.3.3 It is set to `false` in every browser. 411 * @deprecated 4.3.3 It is set to `false` in every browser.
@@ -438,7 +459,7 @@
438 if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) ) { 459 if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) ) {
439 if ( emailProtection == 'encode' ) { 460 if ( emailProtection == 'encode' ) {
440 href = href.replace( encodedEmailLinkRegex, function( match, protectedAddress, rest ) { 461 href = href.replace( encodedEmailLinkRegex, function( match, protectedAddress, rest ) {
441 // Without it 'undefined' is appended to e-mails without subject and body (#9192). 462 // Without it 'undefined' is appended to e-mails without subject and body (http://dev.ckeditor.com/ticket/9192).
442 rest = rest || ''; 463 rest = rest || '';
443 464
444 return 'mailto:' + 465 return 'mailto:' +
@@ -513,7 +534,7 @@
513 534
514 var featureMatch; 535 var featureMatch;
515 while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[ 2 ] ) ) ) { 536 while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[ 2 ] ) ) ) {
516 // Some values should remain numbers (#7300) 537 // Some values should remain numbers (http://dev.ckeditor.com/ticket/7300)
517 if ( ( featureMatch[ 2 ] == 'yes' || featureMatch[ 2 ] == '1' ) && !( featureMatch[ 1 ] in { height: 1, width: 1, top: 1, left: 1 } ) ) 538 if ( ( featureMatch[ 2 ] == 'yes' || featureMatch[ 2 ] == '1' ) && !( featureMatch[ 1 ] in { height: 1, width: 1, top: 1, left: 1 } ) )
518 retval.target[ featureMatch[ 1 ] ] = true; 539 retval.target[ featureMatch[ 1 ] ] = true;
519 else if ( isFinite( featureMatch[ 2 ] ) ) 540 else if ( isFinite( featureMatch[ 2 ] ) )
@@ -527,6 +548,11 @@
527 }; 548 };
528 } 549 }
529 550
551 var download = element.getAttribute( 'download' );
552 if ( download !== null ) {
553 retval.download = true;
554 }
555
530 var advanced = {}; 556 var advanced = {};
531 557
532 for ( var a in advAttrNames ) { 558 for ( var a in advAttrNames ) {
@@ -666,6 +692,11 @@
666 } 692 }
667 } 693 }
668 694
695 // Force download attribute.
696 if ( data.download ) {
697 set.download = '';
698 }
699
669 // Advanced attributes. 700 // Advanced attributes.
670 if ( data.advanced ) { 701 if ( data.advanced ) {
671 for ( var a in advAttrNames ) { 702 for ( var a in advAttrNames ) {
@@ -679,7 +710,7 @@
679 set[ 'data-cke-saved-name' ] = set.name; 710 set[ 'data-cke-saved-name' ] = set.name;
680 } 711 }
681 712
682 // Browser need the "href" fro copy/paste link to work. (#6641) 713 // Browser need the "href" fro copy/paste link to work. (http://dev.ckeditor.com/ticket/6641)
683 if ( set[ 'data-cke-saved-href' ] ) 714 if ( set[ 'data-cke-saved-href' ] )
684 set.href = set[ 'data-cke-saved-href' ]; 715 set.href = set[ 'data-cke-saved-href' ];
685 716
@@ -687,7 +718,8 @@
687 target: 1, 718 target: 1,
688 onclick: 1, 719 onclick: 1,
689 'data-cke-pa-onclick': 1, 720 'data-cke-pa-onclick': 1,
690 'data-cke-saved-name': 1 721 'data-cke-saved-name': 1,
722 'download': 1
691 }; 723 };
692 724
693 if ( data.advanced ) 725 if ( data.advanced )
@@ -701,6 +733,41 @@
701 set: set, 733 set: set,
702 removed: CKEDITOR.tools.objectKeys( removed ) 734 removed: CKEDITOR.tools.objectKeys( removed )
703 }; 735 };
736 },
737
738
739 /**
740 * Determines whether an element should have a "Display Text" field in the Link dialog.
741 *
742 * @since 4.5.11
743 * @param {CKEDITOR.dom.element/null} element Selected element, `null` if none selected or if a ranged selection
744 * is made.
745 * @param {CKEDITOR.editor} editor The editor instance for which the check is performed.
746 * @returns {Boolean}
747 */
748 showDisplayTextForElement: function( element, editor ) {
749 var undesiredElements = {
750 img: 1,
751 table: 1,
752 tbody: 1,
753 thead: 1,
754 tfoot: 1,
755 input: 1,
756 select: 1,
757 textarea: 1
758 },
759 selection = editor.getSelection();
760
761 // Widget duck typing, we don't want to show display text for widgets.
762 if ( editor.widgets && editor.widgets.focused ) {
763 return false;
764 }
765
766 if ( selection && selection.getRanges().length > 1 ) {
767 return false;
768 }
769
770 return !element || !element.getName || !element.is( undesiredElements );
704 } 771 }
705 }; 772 };
706 773
@@ -709,8 +776,29 @@
709 CKEDITOR.unlinkCommand = function() {}; 776 CKEDITOR.unlinkCommand = function() {};
710 CKEDITOR.unlinkCommand.prototype = { 777 CKEDITOR.unlinkCommand.prototype = {
711 exec: function( editor ) { 778 exec: function( editor ) {
779 // IE/Edge removes link from selection while executing "unlink" command when cursor
780 // is right before/after link's text. Therefore whole link must be selected and the
781 // position of cursor must be restored to its initial state after unlinking. (http://dev.ckeditor.com/ticket/13062)
782 if ( CKEDITOR.env.ie ) {
783 var range = editor.getSelection().getRanges()[ 0 ],
784 link = ( range.getPreviousEditableNode() && range.getPreviousEditableNode().getAscendant( 'a', true ) ) ||
785 ( range.getNextEditableNode() && range.getNextEditableNode().getAscendant( 'a', true ) ),
786 bookmark;
787
788 if ( range.collapsed && link ) {
789 bookmark = range.createBookmark();
790 range.selectNodeContents( link );
791 range.select();
792 }
793 }
794
712 var style = new CKEDITOR.style( { element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 } ); 795 var style = new CKEDITOR.style( { element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 } );
713 editor.removeStyle( style ); 796 editor.removeStyle( style );
797
798 if ( bookmark ) {
799 range.moveToBookmark( bookmark );
800 range.select();
801 }
714 }, 802 },
715 803
716 refresh: function( editor, path ) { 804 refresh: function( editor, path ) {
@@ -727,7 +815,8 @@
727 815
728 contextSensitive: 1, 816 contextSensitive: 1,
729 startDisabled: 1, 817 startDisabled: 1,
730 requiredContent: 'a[href]' 818 requiredContent: 'a[href]',
819 editorFocus: 1
731 }; 820 };
732 821
733 CKEDITOR.removeAnchorCommand = function() {}; 822 CKEDITOR.removeAnchorCommand = function() {};