From a4d2dbb4b537de47a2daa8965909293c0603a4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Tue, 17 Mar 2015 20:08:46 +0100 Subject: [PATCH 01/33] Embed plugins' initial commit. --- plugins/embed/icons/embed.png | Bin 0 -> 389 bytes plugins/embed/icons/hidpi/embed.png | Bin 0 -> 728 bytes plugins/embed/plugin.js | 51 +++++ plugins/embedbase/dialogs/embedbase.js | 79 ++++++++ plugins/embedbase/lang/en.js | 11 ++ plugins/embedbase/plugin.js | 180 ++++++++++++++++++ plugins/embedsemantic/icons/embedsemantic.png | Bin 0 -> 389 bytes .../icons/hidpi/embedsemantic.png | Bin 0 -> 728 bytes plugins/embedsemantic/plugin.js | 91 +++++++++ tests/plugins/embed/embed.html | 57 ++++++ tests/plugins/embed/embed.md | 3 + .../plugins/embedsemantic/embedsemantic.html | 47 +++++ tests/plugins/embedsemantic/embedsemantic.md | 3 + 13 files changed, 522 insertions(+) create mode 100644 plugins/embed/icons/embed.png create mode 100644 plugins/embed/icons/hidpi/embed.png create mode 100644 plugins/embed/plugin.js create mode 100644 plugins/embedbase/dialogs/embedbase.js create mode 100644 plugins/embedbase/lang/en.js create mode 100644 plugins/embedbase/plugin.js create mode 100644 plugins/embedsemantic/icons/embedsemantic.png create mode 100644 plugins/embedsemantic/icons/hidpi/embedsemantic.png create mode 100644 plugins/embedsemantic/plugin.js create mode 100644 tests/plugins/embed/embed.html create mode 100644 tests/plugins/embed/embed.md create mode 100644 tests/plugins/embedsemantic/embedsemantic.html create mode 100644 tests/plugins/embedsemantic/embedsemantic.md diff --git a/plugins/embed/icons/embed.png b/plugins/embed/icons/embed.png new file mode 100644 index 0000000000000000000000000000000000000000..9a9a73568eff8443f69985e68bc1f8039940b498 GIT binary patch literal 389 zcmV;00eb$4P)Px$KS@MER5%f(Q#)>iFc8HOqRj=oh@Jw`(NaK@3vh*;Ag4&bN8kb}5CsiA1sxIs z5ky4}0OAwRWM{(?uf%FyFmHZup0Ujov4S9Y)imuaAYa#2VT>tYVG#yPOW?0z7~aA# z{8S;Rs_Hq*GEM~t?8&k$7iI{osX=&10m<1nga5Dsl1^j~DUeN*#eiJ;4`kCw z2@v_dzruxzqKFkmfvKBgyf=+Vw*RUuOFW&MQ;;bDQrC6yevoYPlO$nCFpeV*xu-x( z!@OY__%^DlLJL78===WLahz4W2`t=x(lljF)1X{k*HH=iR{|mJz3aLUvB#HfGhog+ jHnnZb>bl;ipZ9+N?)IpPx%l1W5CR9Fe^SIvqVK@c7_f;k1eB@F0EauCGaB_N2;5WM;fd5G*Qc=aK~gDiNC zh&coW1rd*dL=eHhRCWAntDEV}=DjFtM3B0>v49F*Y*J0X%hdu2k1@cZ$f0nGU?eS#cAKV z*`)%yEE@dOb2p9<(5yIU(zA65q9V;^(|VWmdOdUV&t@~r#7n?J-LTM^>IaXTs1VQw z91oe?5Zjn=AOzOF=kxi)V~~(SKpWf|RNJvXH(Ra8+#;Y2lJ)>9Rj=2rGuOU2nAWpy zFc_Fh2mxItu)9XGJxb02cSS&0=u~?KflX3M>%Kw4w zcAJPX3|-0D`Yx`HtAo8%;3th-F88KZt666WpqA_mB_0lk<}8=XrPbYqLP7j6-qpjO zRDeL=*6Ve!*=z#(JTos0uto+f^_=y-0W9!7oUksHO7C~O-B&Vr?E@#-U#(WgXBdVS zJM6nYqyp3F^gA8F2l{v%(x=le7pT|B92L?MIAD0K=g|L`sK9S=!OemeK%%Pv0000< KMNUMnLSTZg%~a6< literal 0 HcmV?d00001 diff --git a/plugins/embed/plugin.js b/plugins/embed/plugin.js new file mode 100644 index 00000000000..74c0de24b86 --- /dev/null +++ b/plugins/embed/plugin.js @@ -0,0 +1,51 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +( function() { + 'use strict'; + + CKEDITOR.plugins.add( 'embed', { + icons: 'embed', // %REMOVE_LINE_CORE% + hidpi: true, // %REMOVE_LINE_CORE% + requires: 'embedbase', + + init: function( editor ) { + var widgetDefinition = CKEDITOR.plugins.embed.createWidgetBaseDefinition( editor ); + + CKEDITOR.tools.extend( widgetDefinition, { + dialog: 'embedBase', + button: editor.lang.embedbase.button, + allowedContent: 'div[!data-oembed-url]', + requiredContent: 'div[data-oembed-url]', + providerUrl: new CKEDITOR.template( + editor.config.embed_provider || + '//ckeditor.iframe.ly/api/oembed?url={url}&callback={callback}' + ), + + upcast: function( el, data ) { + if ( el.name == 'div' && el.attributes[ 'data-oembed-url' ] ) { + data.url = el.attributes[ 'data-oembed-url' ]; + + return true; + } + }, + + downcast: function( el ) { + el.attributes[ 'data-oembed-url' ] = this.data.url; + } + }, true ); + + editor.widgets.add( 'embed', widgetDefinition ); + + // Do not filter contents of the div[data-oembed-url] at all. + editor.filter.addElementCallback( function( el ) { + if ( 'data-oembed-url' in el.attributes ) { + return CKEDITOR.FILTER_SKIP_TREE; + } + } ); + } + } ); + +} )(); \ No newline at end of file diff --git a/plugins/embedbase/dialogs/embedbase.js b/plugins/embedbase/dialogs/embedbase.js new file mode 100644 index 00000000000..f92a06265a6 --- /dev/null +++ b/plugins/embedbase/dialogs/embedbase.js @@ -0,0 +1,79 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/* global alert */ + +CKEDITOR.dialog.add( 'embedBase', function( editor ) { + 'use strict'; + + var lang = editor.lang.embedbase; + + return { + title: lang.title, + minWidth: 350, + minHeight: 50, + + onLoad: function() { + var that = this, + okButton = that.getButton( 'ok' ); + + this.on( 'ok', function( evt ) { + that.widget.loadContent( + that.getValueOf( 'info', 'url' ), + function() { + editor.widgets.finalizeCreation( that.widget.wrapper.getParent( true ) ); + + that.hide(); + okButton.enable(); + }, + function() { + that.getContentElement( 'info', 'url' ).select(); + + // We need to enable the OK button so user can fix the URL. + okButton.enable(); + + alert( lang.fetchingFailed ); + } + ); + + // We're going to hide it manually, after remote response is fetched. + evt.data.hide = false; + + // Disable the OK button for the time of loading, so user can't trigger multiple inserts. + okButton.disable(); + + // We don't want the widget system to finalize widget insertion (it happens with priority 20). + evt.stop(); + }, null, null, 15 ); + }, + + contents: [ + { + id: 'info', + + elements: [ + { + type: 'text', + id: 'url', + label: lang.url, + + setup: function( widget ) { + this.setValue( widget.data.url ); + }, + + validate: function() { + // TODO + // if ( !plugin.validateUrl( this.getValue() ) ) { + // return lang.invalidUrl; + // } + + return true; + } + } + ] + } + ] + }; +} ); \ No newline at end of file diff --git a/plugins/embedbase/lang/en.js b/plugins/embedbase/lang/en.js new file mode 100644 index 00000000000..74be64a4e9d --- /dev/null +++ b/plugins/embedbase/lang/en.js @@ -0,0 +1,11 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'embedbase', 'en', { + pathName: 'media object', + title: 'Media Embed', + url: 'URL', + fetchingFailed: 'Failed to fetch content for a given URL.', + button: 'Insert Media Embed' +} ); diff --git a/plugins/embedbase/plugin.js b/plugins/embedbase/plugin.js new file mode 100644 index 00000000000..c724c39725b --- /dev/null +++ b/plugins/embedbase/plugin.js @@ -0,0 +1,180 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +( function() { + 'use strict'; + + CKEDITOR.plugins.add( 'embedbase', { + lang: 'en', // %REMOVE_LINE_CORE% + requires: 'widget,notificationaggregator', + + onLoad: function() { + CKEDITOR._.jsonpCallbacks = {}; + }, + + init: function() { + CKEDITOR.dialog.add( 'embedBase', this.path + 'dialogs/embedbase.js' ); + } + } ); + + function createWidgetBaseDefinition( editor ) { + return { + mask: true, + template: '
', + pathName: editor.lang.embedbase.pathName, + // This cache object will be used between all instances of this widget. + _cache: {}, + + init: function() { + this.on( 'sendRequest', function( evt ) { + this.sendRequest( evt.data ); + }, this, null, 999 ); + + // Expose the widget in the dialog - needed to trigger loadContent() and do error handling. + this.on( 'dialog', function( evt ) { + evt.data.widget = this; + }, this ); + + this.on( 'handleResponse', function( evt ) { + if ( !evt.data.html ) { + evt.data.html = this.responseToHtml( evt.data.url, evt.data.response ); + } + }, this ); + }, + + cacheResponse: function( url, response ) { + this._cache[ url ] = response; + }, + + getCachedResponse: function( url ) { + return this._cache[ url ]; + }, + + // We can't load content on #data, because that would make it rather impossible + // to listen to load success and error. + loadContent: function( url, callback, errorCallback ) { + var cached = this.getCachedResponse( url ), + that = this; + + if ( cached ) { + that.handleResponse( url, cached ); + callback && callback(); + return; + } + + this.fire( 'sendRequest', { + url: url, + + callback: function( response ) { + that.cacheResponse( url, response ); + that.handleResponse( url, response ); + callback && callback(); + }, + + errorCallback: function() { + errorCallback && errorCallback(); + } + } ); + }, + + sendRequest: function( opts ) { + Jsonp.sendRequest( + this.providerUrl, + { + url: encodeURIComponent( opts.url ) + }, + opts.callback, + opts.errorCallback + ); + }, + + handleResponse: function( url, response ) { + var evtData = { + url: url, + html: '', + response: response + }; + + if ( this.fire( 'handleResponse', evtData ) !== false ) { + this.setContent( url, evtData.html ); + } + }, + + responseToHtml: function( url, response ) { + if ( response.type == 'photo' ) { + return ''; + } else if ( response.type == 'link' ) { + var title = CKEDITOR.tools.htmlEncodeAttr( response.title || '' ), + // In case of the link type response may not contain url. + linkUrl = CKEDITOR.tools.htmlEncodeAttr( response.url || url ); + + return '' + CKEDITOR.tools.htmlEncode( linkUrl ) + ''; + } + + // Types: video, rich. + return response.html; + }, + + setContent: function( url, content ) { + this.setData( 'url', url ); + this.element.setHtml( content ); + } + }; + } + + var Jsonp = { + _attachScript: function( url, errorCallback ) { + // ATM we can't use CKE scriptloader here, because it will make sure that script + // with given URL is added only once. + var script = new CKEDITOR.dom.element( 'script' ); + script.setAttribute( 'src', url ); + + if ( errorCallback ) { + script.on( 'error', function( evt ) { + script.remove(); + errorCallback( evt.data ); + } ); + } + + CKEDITOR.document.getBody().append( script ); + + return script; + }, + + sendRequest: function( urlTemplate, urlParams, callback, errorCallback ) { + urlParams = urlParams || {}; + + var callbackKey = CKEDITOR.tools.getNextNumber(), + scriptElement; + + urlParams.callback = 'CKEDITOR._.jsonpCallbacks[' + callbackKey + ']'; + + CKEDITOR._.jsonpCallbacks[ callbackKey ] = function( response ) { + removeListener(); + scriptElement.remove(); + callback( response ); + }; + + scriptElement = this._attachScript( urlTemplate.output( urlParams ), function( data ) { + // removeListener() does not remove the element, because scriptElement may not exist at this point yet. + removeListener(); + errorCallback( data ); + } ); + + function removeListener() { + delete CKEDITOR._.jsonpCallbacks[ callbackKey ]; + } + } + }; + + CKEDITOR.plugins.embed = { + createWidgetBaseDefinition: createWidgetBaseDefinition + }; + +} )(); \ No newline at end of file diff --git a/plugins/embedsemantic/icons/embedsemantic.png b/plugins/embedsemantic/icons/embedsemantic.png new file mode 100644 index 0000000000000000000000000000000000000000..9a9a73568eff8443f69985e68bc1f8039940b498 GIT binary patch literal 389 zcmV;00eb$4P)Px$KS@MER5%f(Q#)>iFc8HOqRj=oh@Jw`(NaK@3vh*;Ag4&bN8kb}5CsiA1sxIs z5ky4}0OAwRWM{(?uf%FyFmHZup0Ujov4S9Y)imuaAYa#2VT>tYVG#yPOW?0z7~aA# z{8S;Rs_Hq*GEM~t?8&k$7iI{osX=&10m<1nga5Dsl1^j~DUeN*#eiJ;4`kCw z2@v_dzruxzqKFkmfvKBgyf=+Vw*RUuOFW&MQ;;bDQrC6yevoYPlO$nCFpeV*xu-x( z!@OY__%^DlLJL78===WLahz4W2`t=x(lljF)1X{k*HH=iR{|mJz3aLUvB#HfGhog+ jHnnZb>bl;ipZ9+N?)IpPx%l1W5CR9Fe^SIvqVK@c7_f;k1eB@F0EauCGaB_N2;5WM;fd5G*Qc=aK~gDiNC zh&coW1rd*dL=eHhRCWAntDEV}=DjFtM3B0>v49F*Y*J0X%hdu2k1@cZ$f0nGU?eS#cAKV z*`)%yEE@dOb2p9<(5yIU(zA65q9V;^(|VWmdOdUV&t@~r#7n?J-LTM^>IaXTs1VQw z91oe?5Zjn=AOzOF=kxi)V~~(SKpWf|RNJvXH(Ra8+#;Y2lJ)>9Rj=2rGuOU2nAWpy zFc_Fh2mxItu)9XGJxb02cSS&0=u~?KflX3M>%Kw4w zcAJPX3|-0D`Yx`HtAo8%;3th-F88KZt666WpqA_mB_0lk<}8=XrPbYqLP7j6-qpjO zRDeL=*6Ve!*=z#(JTos0uto+f^_=y-0W9!7oUksHO7C~O-B&Vr?E@#-U#(WgXBdVS zJM6nYqyp3F^gA8F2l{v%(x=le7pT|B92L?MIAD0K=g|L`sK9S=!OemeK%%Pv0000< KMNUMnLSTZg%~a6< literal 0 HcmV?d00001 diff --git a/plugins/embedsemantic/plugin.js b/plugins/embedsemantic/plugin.js new file mode 100644 index 00000000000..02bdb0396a1 --- /dev/null +++ b/plugins/embedsemantic/plugin.js @@ -0,0 +1,91 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +( function() { + 'use strict'; + + CKEDITOR.plugins.add( 'embedsemantic', { + icons: 'embedsemantic', // %REMOVE_LINE_CORE% + hidpi: true, // %REMOVE_LINE_CORE% + requires: 'embedbase', + + onLoad: function() { + this.registerOembedTag(); + }, + + init: function( editor ) { + var widgetDefinition = CKEDITOR.plugins.embed.createWidgetBaseDefinition( editor ), + origInit = widgetDefinition.init; + + CKEDITOR.tools.extend( widgetDefinition, { + dialog: 'embedBase', + button: editor.lang.embedbase.button, + allowedContent: 'oembed', + requiredContent: 'oembed', + // Share config with the embed plugin. + providerUrl: new CKEDITOR.template( + editor.config.embed_provider || + '//ckeditor.iframe.ly/api/oembed?url={url}&callback={callback}' + ), + + init: function() { + origInit.call( this ); + + // Need to wait for #ready with the initial content loading, because on #init there's no data yet. + this.once( 'ready', function() { + // When widget is created using dialog, the dialog's code will handle loading the content + // (because it handles success and error), so do load the content only when loading data. + if ( this.data.loadOnReady ) { + this.loadContent( this.data.url ); + } + } ); + }, + + upcast: function( element, data ) { + if ( element.name != 'oembed' ) { + return; + } + + var text = element.children[ 0 ], + div; + + if ( text && text.type == CKEDITOR.NODE_TEXT && text.value ) { + data.url = text.value; + data.loadOnReady = true; + div = new CKEDITOR.htmlParser.element( 'div' ); + element.replaceWith( div ); + return div; + } + }, + + downcast: function() { + var ret = new CKEDITOR.htmlParser.element( 'oembed' ); + ret.add( new CKEDITOR.htmlParser.text( this.data.url ) ); + + return ret; + } + }, true ); + + editor.widgets.add( 'embedSemantic', widgetDefinition ); + }, + + registerOembedTag: function() { + var dtd = CKEDITOR.dtd, + name; + + // The oembed tag may contain text only. + dtd.oembed = { '#': 1 }; + + // Register oembed tag as allowed child, in each tag that can contain a div. + // It also registers the oembed tag in objects like $block, $blockLimit, etc. + for ( name in dtd ) { + if ( dtd[ name ].div ) { + dtd[ name ].oembed = 1; + } + } + } + } ); + +} )(); \ No newline at end of file diff --git a/tests/plugins/embed/embed.html b/tests/plugins/embed/embed.html new file mode 100644 index 00000000000..85e05a1bc01 --- /dev/null +++ b/tests/plugins/embed/embed.html @@ -0,0 +1,57 @@ +

Test URLs

+ +
    +
  • https://twitter.com/reinmarpl/status/573118615274315776
  • +
  • https://www.youtube.com/watch?v=GUl9_5kK9ts
  • +
  • https://instagram.com/p/wZB11CIfqX/
  • +
  • https://www.google.dk/maps/place/Zygmunta+S%C5%82omi%C5%84skiego+15,+Warszawa
  • +
  • http://tellto.tumblr.com/post/112160646328
  • +
  • https://www.flickr.com/photos/polandmfa/7005560084/
  • +
  • http://i.imgur.com/Z3ilPBI.jpg
  • +
  • https://open.spotify.com/track/2XsTC2dPbmWREu5RQ1mQC0
  • +
+ +

Empty editor

+ +
+

Foo bar.

+
+ +

Dat rich editor

+ +
+

Foo bar

+ +
+ + +
+ +

Foo bar.

+ +
Imgur
+ +

Foo bar.

+ +
+
+
+
+
+ +
+ + \ No newline at end of file diff --git a/tests/plugins/embed/embed.md b/tests/plugins/embed/embed.md new file mode 100644 index 00000000000..2a9fb87df11 --- /dev/null +++ b/tests/plugins/embed/embed.md @@ -0,0 +1,3 @@ +@bender-ui: collapsed + +1. Click. diff --git a/tests/plugins/embedsemantic/embedsemantic.html b/tests/plugins/embedsemantic/embedsemantic.html new file mode 100644 index 00000000000..af1d6932a87 --- /dev/null +++ b/tests/plugins/embedsemantic/embedsemantic.html @@ -0,0 +1,47 @@ +

Test URLs

+ +
    +
  • https://twitter.com/reinmarpl/status/573118615274315776
  • +
  • https://www.youtube.com/watch?v=GUl9_5kK9ts
  • +
  • https://instagram.com/p/wZB11CIfqX/
  • +
  • https://www.google.dk/maps/place/Zygmunta+S%C5%82omi%C5%84skiego+15,+Warszawa
  • +
  • http://tellto.tumblr.com/post/112160646328
  • +
  • https://www.flickr.com/photos/polandmfa/7005560084/
  • +
  • http://i.imgur.com/Z3ilPBI.jpg
  • +
  • https://open.spotify.com/track/2XsTC2dPbmWREu5RQ1mQC0
  • +
+ +

Empty editor

+ +
+

Foo bar.

+
+ +

Dat rich editor

+ +
+

Foo bar

+ + https://twitter.com/reinmarpl/status/573118615274315776 + +

Foo bar.

+ + http://i.imgur.com/Z3ilPBI.jpg + +

Foo bar.

+ + https://www.google.dk/maps/place/Zygmunta+S%C5%82omi%C5%84skiego+15,+Warszawa +
+ + \ No newline at end of file diff --git a/tests/plugins/embedsemantic/embedsemantic.md b/tests/plugins/embedsemantic/embedsemantic.md new file mode 100644 index 00000000000..2a9fb87df11 --- /dev/null +++ b/tests/plugins/embedsemantic/embedsemantic.md @@ -0,0 +1,3 @@ +@bender-ui: collapsed + +1. Click. From a324e0ecb0436774f77283792f3b0cdbeeac45d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Wed, 18 Mar 2015 11:39:45 +0100 Subject: [PATCH 02/33] Integrated notifications, added request delays in the manual tests, switched to textareas to avoid scripts execution :(. --- plugins/embed/plugin.js | 2 +- plugins/embedbase/dialogs/embedbase.js | 14 ++--- plugins/embedbase/lang/en.js | 7 ++- plugins/embedbase/plugin.js | 53 +++++++++++++++---- plugins/embedsemantic/plugin.js | 2 +- tests/plugins/embed/embed.html | 37 +++++++++---- .../plugins/embedsemantic/embedsemantic.html | 38 +++++++++---- 7 files changed, 116 insertions(+), 37 deletions(-) diff --git a/plugins/embed/plugin.js b/plugins/embed/plugin.js index 74c0de24b86..259f80c46b6 100644 --- a/plugins/embed/plugin.js +++ b/plugins/embed/plugin.js @@ -12,7 +12,7 @@ requires: 'embedbase', init: function( editor ) { - var widgetDefinition = CKEDITOR.plugins.embed.createWidgetBaseDefinition( editor ); + var widgetDefinition = CKEDITOR.plugins.embedBase.createWidgetBaseDefinition( editor ); CKEDITOR.tools.extend( widgetDefinition, { dialog: 'embedBase', diff --git a/plugins/embedbase/dialogs/embedbase.js b/plugins/embedbase/dialogs/embedbase.js index f92a06265a6..6d23fe53be5 100644 --- a/plugins/embedbase/dialogs/embedbase.js +++ b/plugins/embedbase/dialogs/embedbase.js @@ -20,23 +20,25 @@ CKEDITOR.dialog.add( 'embedBase', function( editor ) { okButton = that.getButton( 'ok' ); this.on( 'ok', function( evt ) { - that.widget.loadContent( - that.getValueOf( 'info', 'url' ), - function() { + that.widget.loadContent( that.getValueOf( 'info', 'url' ), { + noNotifications: true, + + callback: function() { editor.widgets.finalizeCreation( that.widget.wrapper.getParent( true ) ); that.hide(); okButton.enable(); }, - function() { + + errorCallback: function() { that.getContentElement( 'info', 'url' ).select(); // We need to enable the OK button so user can fix the URL. okButton.enable(); - alert( lang.fetchingFailed ); + alert( lang.fetchingGivenFailed ); } - ); + } ); // We're going to hide it manually, after remote response is fetched. evt.data.hide = false; diff --git a/plugins/embedbase/lang/en.js b/plugins/embedbase/lang/en.js index 74be64a4e9d..0cc7558ac3f 100644 --- a/plugins/embedbase/lang/en.js +++ b/plugins/embedbase/lang/en.js @@ -6,6 +6,9 @@ CKEDITOR.plugins.setLang( 'embedbase', 'en', { pathName: 'media object', title: 'Media Embed', url: 'URL', - fetchingFailed: 'Failed to fetch content for a given URL.', - button: 'Insert Media Embed' + button: 'Insert Media Embed', + fetchingGivenFailed: 'Failed to fetch content for a given URL.', + fetchingSpecificFailed: 'Failed to fetch content for {url}.', + fetchingOne: 'Fetching oEmbed response...', + fetchingMany: 'Fetching oEmbed responses, {current} of {max} done...' } ); diff --git a/plugins/embedbase/plugin.js b/plugins/embedbase/plugin.js index c724c39725b..d29007a011b 100644 --- a/plugins/embedbase/plugin.js +++ b/plugins/embedbase/plugin.js @@ -20,11 +20,14 @@ } ); function createWidgetBaseDefinition( editor ) { + var aggregator, + lang = editor.lang.embedbase; + return { mask: true, template: '
', - pathName: editor.lang.embedbase.pathName, - // This cache object will be used between all instances of this widget. + pathName: lang.pathName, + // This cache object will be shared between all instances of this widget. _cache: {}, init: function() { @@ -54,27 +57,46 @@ // We can't load content on #data, because that would make it rather impossible // to listen to load success and error. - loadContent: function( url, callback, errorCallback ) { + loadContent: function( url, opts ) { + opts = opts || {}; + var cached = this.getCachedResponse( url ), - that = this; + that = this, + task; if ( cached ) { that.handleResponse( url, cached ); - callback && callback(); + opts.callback && opts.callback(); return; } + if ( !opts.noNotifications ) { + task = this.createTask(); + } + this.fire( 'sendRequest', { url: url, callback: function( response ) { + // TODO move task.done() to handleResponse() and make task part of the "request" object. + task && task.done(); + that.cacheResponse( url, response ); that.handleResponse( url, response ); - callback && callback(); + + opts.callback && opts.callback(); }, errorCallback: function() { - errorCallback && errorCallback(); + // TODO create handleError(). + if ( task ) { + task.cancel(); + + var warningMsg = new CKEDITOR.template( lang.fetchingSpecificFailed ).output( { url: url.slice( 0, 40 ) + '...' } ); + editor.showNotification( warningMsg, 'warning' ); + } + + opts.errorCallback && opts.errorCallback(); } } ); }, @@ -124,6 +146,18 @@ setContent: function( url, content ) { this.setData( 'url', url ); this.element.setHtml( content ); + }, + + createTask: function() { + if ( !aggregator || aggregator.isFinished() ) { + aggregator = new CKEDITOR.plugins.notificationAggregator( editor, lang.fetchingMany, lang.fetchingOne ); + + aggregator.on( 'finished', function() { + aggregator.notification.hide(); + } ); + } + + return aggregator.createTask(); } }; } @@ -173,8 +207,9 @@ } }; - CKEDITOR.plugins.embed = { - createWidgetBaseDefinition: createWidgetBaseDefinition + CKEDITOR.plugins.embedBase = { + createWidgetBaseDefinition: createWidgetBaseDefinition, + _jsonp: Jsonp }; } )(); \ No newline at end of file diff --git a/plugins/embedsemantic/plugin.js b/plugins/embedsemantic/plugin.js index 02bdb0396a1..e84cabfb9a9 100644 --- a/plugins/embedsemantic/plugin.js +++ b/plugins/embedsemantic/plugin.js @@ -16,7 +16,7 @@ }, init: function( editor ) { - var widgetDefinition = CKEDITOR.plugins.embed.createWidgetBaseDefinition( editor ), + var widgetDefinition = CKEDITOR.plugins.embedBase.createWidgetBaseDefinition( editor ), origInit = widgetDefinition.init; CKEDITOR.tools.extend( widgetDefinition, { diff --git a/tests/plugins/embed/embed.html b/tests/plugins/embed/embed.html index 85e05a1bc01..acd23f7b480 100644 --- a/tests/plugins/embed/embed.html +++ b/tests/plugins/embed/embed.html @@ -11,15 +11,9 @@
  • https://open.spotify.com/track/2XsTC2dPbmWREu5RQ1mQC0
  • -

    Empty editor

    - -
    -

    Foo bar.

    -
    -

    Dat rich editor

    -
    + + +

    Empty editor

    -
    + ", + "rel": [ + "app", + "inline" + ], + "media": { + "min-width": 250, + "max-width": 550 + } + } + ], + "icon": [ + { + "href": "https://abs.twimg.com/favicons/favicon.ico", + "rel": [ + "shortcut", + "icon" + ], + "type": "image/x-icon" + } + ] + }, + "html": "

    We'd like to welcome two new #CKEditor JS Developers: Grzegorz Pabian (@GregPabian) and Artur Delura (@adelura): http://t.co/BB5rzL8Hl8

    — CKSource (@cksource) June 27, 2014
    \n" +}); \ No newline at end of file diff --git a/tests/plugins/embedbase/definition.js b/tests/plugins/embedbase/definition.js new file mode 100644 index 00000000000..bb6b7032339 --- /dev/null +++ b/tests/plugins/embedbase/definition.js @@ -0,0 +1,147 @@ +/* bender-ckeditor-plugins: embedbase,toolbar */ + +'use strict'; + +bender.editors = { + classic: { + name: 'editor_classic', + creator: 'replace' + } +}; + +function createDef( editor ) { + return CKEDITOR.plugins.embedBase.createWidgetBaseDefinition( editor ); +} + +bender.test( { + spies: [], + + tearDown: function() { + var spy; + + while ( spy = this.spies.pop() ) { + spy.restore(); + } + }, + + 'test def._cacheResponse, def._getCachedResponse': function() { + var def1 = createDef( this.editors.classic ), + def2 = createDef( this.editors.classic ); + + def1._cacheResponse( 'a', 1 ); + assert.areSame( 1, def1._getCachedResponse( 'a' ), 'def1.get a' ); + assert.isUndefined( def2._getCachedResponse( 'a' ), 'def2.get a - cache per definition' ); + }, + + 'test def._createTask returned value': function() { + var def1 = createDef( this.editors.classic ), + def2 = createDef( this.editors.classic ), + ret = 1; + + this.spies.push( sinon.stub( CKEDITOR.plugins.notificationAggregator.prototype, 'createTask', function() { + return ret++; + } ) ); + + assert.areSame( 1, def1._createTask() ); + assert.areSame( 2, def2._createTask() ); + }, + + 'test def._createTask creates new aggregator once all tasks are finished': function() { + var origCreateTask = CKEDITOR.plugins.notificationAggregator.prototype.createTask, + def1 = createDef( this.editors.classic ), + def2 = createDef( this.editors.classic ), + aggregators = []; + + this.spies.push( sinon.stub( CKEDITOR.plugins.notificationAggregator.prototype, 'createTask', function() { + aggregators.push( this ); + + return origCreateTask.call( this ); + } ) ); + + var task1 = def1._createTask(); + def2._createTask(); + assert.areNotSame( aggregators[ 0 ], aggregators[ 1 ], 'two definitions use different aggregators' ); + + task1.done(); + def1._createTask(); + assert.areNotSame( aggregators[ 0 ], aggregators[ 2 ], 'after all tasks are finished, new notifagg is created' ); + }, + + 'test def._responseToHtml - rich, video': function() { + var def = createDef( this.editors.classic ); + + assert.areSame( 'a', def._responseToHtml( 'http://foo', { type: 'rich', html: 'a' } ), 'rich' ); + assert.areSame( 'b', def._responseToHtml( 'http://foo', { type: 'video', html: 'b' } ), 'video' ); + }, + + 'test def._responseToHtml - photo': function() { + var def = createDef( this.editors.classic ); + + assert.areSame( '', + def._responseToHtml( 'http://foo', { type: 'photo', url: 'a"b' } ), 'no title' ); + + assert.areSame( 'x"y', + def._responseToHtml( 'http://foo', { type: 'photo', url: 'a', title: 'x"y' } ), 'with title' ); + }, + + 'test def._responseToHtml - link': function() { + var def = createDef( this.editors.classic ); + + assert.areSame( 'http://foo"<bar', + def._responseToHtml( 'http://foo"http://foo"<bar', + def._responseToHtml( 'http://foo', { type: 'link', url: 'http://foo"http://foo', + def._responseToHtml( 'http://foo', { type: 'link', title: 'a"b' } ), 'with title' ); + }, + + 'test def._sendRequest': function() { + var def = createDef( this.editors.classic ), + stub = sinon.stub( CKEDITOR.plugins.embedBase._jsonp, 'sendRequest' ), + request = { + url: 'http://f&y=', + callback: 1, + errorCallback: 2 + }; + + this.spies.push( stub ); + + def.providerUrl = new CKEDITOR.template( 'x' ); + def._sendRequest( request ); + + var args = stub.args[ 0 ]; + + assert.areSame( def.providerUrl, args[ 0 ], 'url pattern' ); + assert.areSame( 'http%3A%2F%2Ff%26y%3D', args[ 1 ].url, 'url param url' ); + assert.areSame( request.callback, args[ 2 ], 'callback' ); + assert.areSame( request.errorCallback, args[ 3 ], 'errorCallback' ); + }, + + 'test def.isUrlValid': function() { + var def = createDef( this.editors.classic ); + CKEDITOR.event.implementOn( def ); + + assert.isTrue( def.isUrlValid( 'http://xxx' ), '1' ); + assert.isTrue( def.isUrlValid( 'https://ąść.mobifoo/*&^%$#?&^%$.xx' ), '2' ); + assert.isTrue( def.isUrlValid( '//xxx.pl/foo' ), '3' ); + assert.isFalse( def.isUrlValid( 'x' ), '4' ); + }, + + 'test def.isUrlValid fires validateUrl': function() { + var def = createDef( this.editors.classic ); + CKEDITOR.event.implementOn( def ); + + def.once( 'validateUrl', function( evt ) { + assert.areSame( 'http://xxx', evt.data ); + } ); + + assert.isTrue( def.isUrlValid( 'http://xxx' ) ); + + def.once( 'validateUrl', function( evt ) { + evt.cancel(); + } ); + assert.isFalse( def.isUrlValid( 'http://xxx' ) ); + } +} ); \ No newline at end of file diff --git a/tests/plugins/embedbase/staticapi.js b/tests/plugins/embedbase/staticapi.js new file mode 100644 index 00000000000..27c5974a5d1 --- /dev/null +++ b/tests/plugins/embedbase/staticapi.js @@ -0,0 +1,24 @@ +/* bender-ckeditor-plugins: embedbase */ + +'use strict'; + +bender.editors = { + classic: { + name: 'editor_classic', + creator: 'replace' + } +}; + +bender.test( { + 'test CKEDITOR._.jsonpCallbacks exists': function() { + assert.isObject( CKEDITOR._.jsonpCallbacks ); + }, + + 'test embedBase.createWidgetBaseDefinition': function() { + var def1 = CKEDITOR.plugins.embedBase.createWidgetBaseDefinition( this.editors.classic ), + def2 = CKEDITOR.plugins.embedBase.createWidgetBaseDefinition( this.editors.classic ); + + assert.areNotSame( def1, def2, 'new definition on every call' ); + assert.isObject( def1 ); + } +} ); \ No newline at end of file From 580c89b77011bea9596ea0e53495cc8b704a4471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Thu, 19 Mar 2015 17:35:31 +0100 Subject: [PATCH 07/33] Tests: Added basic integration tests. --- tests/plugins/embed/integration.html | 4 ++ tests/plugins/embed/integration.js | 29 ++++++++++++ tests/plugins/embed/manual/embed.html | 22 +-------- tests/plugins/embed/manual/embed.md | 1 + tests/plugins/embedbase/_helpers/tools.js | 45 +++++++++++++++++++ tests/plugins/embedsemantic/integration.html | 4 ++ tests/plugins/embedsemantic/integration.js | 29 ++++++++++++ .../embedsemantic/manual/embedsemantic.html | 22 +-------- .../embedsemantic/manual/embedsemantic.md | 1 + tests/plugins/widget/_helpers/tools.js | 3 +- 10 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 tests/plugins/embed/integration.html create mode 100644 tests/plugins/embed/integration.js create mode 100644 tests/plugins/embedbase/_helpers/tools.js create mode 100644 tests/plugins/embedsemantic/integration.html create mode 100644 tests/plugins/embedsemantic/integration.js diff --git a/tests/plugins/embed/integration.html b/tests/plugins/embed/integration.html new file mode 100644 index 00000000000..d59cf0b68ea --- /dev/null +++ b/tests/plugins/embed/integration.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/tests/plugins/embed/integration.js b/tests/plugins/embed/integration.js new file mode 100644 index 00000000000..472a890a1c8 --- /dev/null +++ b/tests/plugins/embed/integration.js @@ -0,0 +1,29 @@ +/* bender-ckeditor-plugins: embed,toolbar */ +/* bender-include: ../widget/_helpers/tools.js, ../embedbase/_helpers/tools.js */ +/* global embedTools, widgetTestsTools */ + +'use strict'; + +bender.editors = { + classic: { + name: 'editor_classic', + creator: 'replace' + } +}; + +embedTools.mockJsonp(); + +var tcs = {}; + +widgetTestsTools.addTests( tcs, { + name: 'basic', + widgetName: 'embed', + extraPlugins: 'embed', + initialInstancesNumber: 1, + newData: [ + [ 'info', 'url', 'http://xxx' ] + ], + newWidgetPattern: '

    url:http%3A%2F%2Fxxx

    ' +} ); + +bender.test( tcs ); \ No newline at end of file diff --git a/tests/plugins/embed/manual/embed.html b/tests/plugins/embed/manual/embed.html index acd23f7b480..04a7edd68df 100644 --- a/tests/plugins/embed/manual/embed.html +++ b/tests/plugins/embed/manual/embed.html @@ -44,31 +44,13 @@