From 8f74634057c30439d262fdc566bfee15a27f3b5d Mon Sep 17 00:00:00 2001 From: Roel Meurders Date: Wed, 18 Sep 2019 15:00:17 +0200 Subject: [PATCH 1/5] Updated version numbers --- jw-player/jw-player.php | 4 ++-- jw-player/readme.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jw-player/jw-player.php b/jw-player/jw-player.php index aeaa85a..bb48fc4 100644 --- a/jw-player/jw-player.php +++ b/jw-player/jw-player.php @@ -4,7 +4,7 @@ Plugin URI: http://www.jwplayer.com/ Description: This plugin allows you to easily upload and embed videos using the JW Player. The embedded video links can be signed, making it harder for viewers to steal your content. Author: JW Player -Version: 1.6.1 +Version: 1.6.2 */ define( 'JWPLAYER_PLUGIN_DIR', dirname( __FILE__ ) ); @@ -22,7 +22,7 @@ require_once( JWPLAYER_PLUGIN_DIR . '/include/utils.php' ); // Default settings -define( 'JWPLAYER_PLUGIN_VERSION', '1.6.1' ); +define( 'JWPLAYER_PLUGIN_VERSION', '1.6.2' ); define( 'JWPLAYER_MINIMUM_PHP_VERSION', '5.4.0' ); define( 'JWPLAYER_PLAYER', 'ALJ3XQCI' ); define( 'JWPLAYER_DASHBOARD', 'https://dashboard.jwplayer.com/' ); diff --git a/jw-player/readme.txt b/jw-player/readme.txt index f95b708..d19d8a3 100644 --- a/jw-player/readme.txt +++ b/jw-player/readme.txt @@ -3,7 +3,7 @@ Contributors: LongTail Video Tags: jwplayer, jw, player, jwplatform, video, media, html5 Requires at least: 4.3 Tested up to: 4.8.2 -Stable tag: 1.6.1 +Stable tag: 1.6.2 License: GPLv3 This plugin is no longer supported. JW Player endorses the ilGhera plugin available here: https://wordpress.org/plugins/jw-player-7-for-wp/ From a46165074903cdeb8170637ffb402748bfbc43fd Mon Sep 17 00:00:00 2001 From: Raul Miller Date: Tue, 11 Feb 2020 11:49:41 -0500 Subject: [PATCH 2/5] remove the "no player" misfeature of the jwplugin --- jw-player/include/shortcode.php | 40 ++++----------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/jw-player/include/shortcode.php b/jw-player/include/shortcode.php index 4dcfbf6..d0e12aa 100644 --- a/jw-player/include/shortcode.php +++ b/jw-player/include/shortcode.php @@ -3,7 +3,6 @@ // We use a global variable to keep track of all the players that are embedded // on a page. If multiple videos with the same player are embedded on the // same page the library only needs to be injected once. -$jwplayer_shortcode_embedded_players = array(); // Regular shortcode function. function jwplayer_shortcode_handle( $atts ) { @@ -185,19 +184,12 @@ function jwplayer_shortcode_filter_player_params( $atts ) { // Create the JS embed code for the jwplayer player function jwplayer_shortcode_create_js_embed( $media_hash, $player_hash = null, $params = array() ) { - global $jwplayer_shortcode_embedded_players; $player_hash = ( null === $player_hash ) ? get_option( 'jwplayer_player' ) : $player_hash; $content_mask = jwplayer_get_content_mask(); $protocol = ( is_ssl() && JWPLAYER_CONTENT_MASK === $content_mask ) ? 'https' : 'http'; - if ( in_array( $player_hash, $jwplayer_shortcode_embedded_players, true ) ) { - $player_script = ''; - } else { - // Injecting script tag because there's no way to properly enqueue a javascript - // at this point in the process :'-( - $player_script = true; - $jwplayer_shortcode_embedded_players[] = $player_hash; - } + // Injecting script tag because there's no way to properly enqueue a javascript + // at this point in the process :'-( if ( 'local' == $media_hash ) { $element_id = "jwplayer_" . wp_generate_password( 12, false ) . "_div"; @@ -243,8 +235,7 @@ function jwplayer_shortcode_create_js_embed( $media_hash, $player_hash = null, $ // Redeclare fitVids to stop it from breaking the JW Player embedding. if ( JWPLAYER_DISABLE_FITVIDS ) { - if ( $player_script ) { - return " + return "
"; - } else { // no player script - return " -
- - "; - } } else { - // no fitvids script here. - if ( $player_script ) { - return " + return "
"; - } else { // no player script - return " -
- - "; - } } } From fa588a57893841e836d69fb246476cfc9915ec16 Mon Sep 17 00:00:00 2001 From: Raul Miller Date: Thu, 13 Feb 2020 20:16:52 -0500 Subject: [PATCH 3/5] checkpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly working, but for some reason attributes get lost when the post is saved, despite the claim at https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/ that "If no attribute source is specified, the attribute will be saved to (and read from) the block’s comment delimiter." (Also, seeing CORS policy failure on atype-video-jwplayer for localtest.me, so something's up with the last deployment of that...) --- jw-player/include/gutenberg-block.php | 25 +++ jw-player/jw-player.php | 1 + jw-player/static/js/jwplayer-embed.js | 71 ++++++ jw-player/static/js/jwplayer-selector.js | 262 +++++++++++++++++++++++ 4 files changed, 359 insertions(+) create mode 100644 jw-player/include/gutenberg-block.php create mode 100644 jw-player/static/js/jwplayer-embed.js create mode 100644 jw-player/static/js/jwplayer-selector.js diff --git a/jw-player/include/gutenberg-block.php b/jw-player/include/gutenberg-block.php new file mode 100644 index 0000000..40896d1 --- /dev/null +++ b/jw-player/include/gutenberg-block.php @@ -0,0 +1,25 @@ + 'jwplayer-block-js', + ] + ); +}); diff --git a/jw-player/jw-player.php b/jw-player/jw-player.php index bb48fc4..3800e3f 100644 --- a/jw-player/jw-player.php +++ b/jw-player/jw-player.php @@ -20,6 +20,7 @@ require_once( JWPLAYER_PLUGIN_DIR . '/include/shortcode.php' ); require_once( JWPLAYER_PLUGIN_DIR . '/include/validation.php' ); require_once( JWPLAYER_PLUGIN_DIR . '/include/utils.php' ); +require_once( JWPLAYER_PLUGIN_DIR . '/include/gutenberg-block.php' ); // Default settings define( 'JWPLAYER_PLUGIN_VERSION', '1.6.2' ); diff --git a/jw-player/static/js/jwplayer-embed.js b/jw-player/static/js/jwplayer-embed.js new file mode 100644 index 0000000..70fbc90 --- /dev/null +++ b/jw-player/static/js/jwplayer-embed.js @@ -0,0 +1,71 @@ +let VideoSelector; +(async()=> { + VideoSelector= (await import('./jwplayer-selector.js')).default; +})(); + +(()=> { + const {registerBlockType}= wp.blocks, + {Path, SVG, Spinner, TextControl}= wp.components, + {createElement, Component}= wp.element, + el= createElement; + registerBlockType( + 'jwplayer-embed-block/jwplayer-embed', { + title: 'JWPlayer Video', + icon: el(SVG, { + alt: 'JWPlayer', + viewBox: '0 0 133.48 133.48', + xmlns: 'http://www.w3.org/2000/svg', + width: 24, + height: 24, + role: 'img', + focusable: 'false', + }, + el(Path, { + fill: '#ec0041', + d: 'M125.9.41a4.56 4.56 0 0 0-6 2.21c-1.1 2.3-29 69.66-29 69.66-.9 2.11-1.5 1.91-1.5-.3 0 0 .1-23.45.2-42.8.1-10.52-5.11-16.44-12.43-17.64a14 14 0 0 0-9.62 2.11 20.63 20.63 0 0 0-6.82 8.11c-2.2 4.31-15.83 44-15.83 44-.71 2.1-1.41 2-1.41-.2 0 0-.4-17.14-.8-21.15-.6-6.22-2-16.34-10-16.84-7.52-.5-10.93 8.82-12.83 14.53-1.31 3.81-7.52 24.36-7.52 24.36l-.9 2.1c-.3 1.11-.8 1.11-1.11 0l-.5-1.9a62.57 62.57 0 0 0-1.9-6 8.6 8.6 0 0 0-1.5-3 3.75 3.75 0 0 0-4-1A3.71 3.71 0 0 0 0 59.75a21.8 21.8 0 0 0 .3 3.61C.82 67 3.43 81.2 5 84.31s5 4.71 8.22 3c3-1.61 4.41-5.31 5.61-8.22 1.53-3.71 9.23-25 9.23-25 .8-2.1 1.4-2 1.3.2 0 0-.4 20.85-.4 30.27 0 3.51.3 9.92 1.3 13.33a10.86 10.86 0 0 0 3.74 5.87 9.27 9.27 0 0 0 6.41 1.8 8.63 8.63 0 0 0 3.21-1.1 13.84 13.84 0 0 0 3.61-3.21 58.32 58.32 0 0 0 5.71-9.62c3.71-7.32 13.34-30 13.34-30 .9-2.11 1.5-1.91 1.5.3 0 0-1.2 38.09-1.2 52.62a29.71 29.71 0 0 0 2 10.63 12.8 12.8 0 0 0 6.62 7.31 11.54 11.54 0 0 0 10.42-.6 16.65 16.65 0 0 0 5.81-6 66.8 66.8 0 0 0 4.92-10.53c2.9-7.51 5.31-15.13 7.71-22.85S126.8 12.14 128.1 5.93a7.49 7.49 0 0 0 .2-2.11 4.53 4.53 0 0 0-2.4-3.41z', + }) + ), + category: 'embed', + attributes: { + video: { + type: 'string', + } + }, + edit: class JwBlockEdit extends Component { + constructor(...args) { + super(...args); + this.setVideo= this.setVideo.bind(this); + this.video= undefined; + } + + setVideo(v) { + this.props.setAttributes({video: v.key}); + } + + render() { + const {video}= this.props.attributes; + return el( + 'div', null, + video && el('img', {src: `http://content.jwplatform.com/thumbs/${video}-40.jpg`}), + video ? el('div', null, `[jwplayer ${video}]`) + : VideoSelector + ? el(VideoSelector, {videos: [], onVideoSelect: this.setVideo}) + : el('div', null, + el(Spinner), '... loading VideoSelector') + ) + } + }, + save: class JwBlockSave extends Component { + render (props) { +console.log('SAVE',this,arguments); + const {video}= this.props.attributes; + return el('div', null, video ? `[jwplayer ${video}]` :null); + } + }, + attributes: { + shortcode: { + }, + }, + } + ); +})(); diff --git a/jw-player/static/js/jwplayer-selector.js b/jw-player/static/js/jwplayer-selector.js new file mode 100644 index 0000000..b094c0b --- /dev/null +++ b/jw-player/static/js/jwplayer-selector.js @@ -0,0 +1,262 @@ +/* + * currently overblown: + * -- includes code for managing a list of selected videos + * but only used to select a single video, so that list is never visible (and poorly tested) */ +const { IconButton, Popover, Spinner } = wp.components, + { withInstanceId } = wp.compose, + { withSelect } = wp.data, + { createElement, Component, Fragment } = wp.element, + { decodeEntities } = wp.htmlEntities, + { UP, DOWN, ENTER } = wp.keycodes, + { addQueryArgs } = wp.url, + el= createElement, + idPfx= 'jwvideo-selector', + thumbnailStyle = { + borderRadius: '3px', + height:'50px', + margin: '2px', + overflow: 'hidden', + width: '50px', + }, + videoListStyle = { + alignItems: 'center', + background: '#f9f9f9', + border: '1px solid #ccc', + borderRadius: '3px', + display: 'flex', + flexWrap: 'nowrap', + justifyContent: 'flex-start', + marginBottom: '3px', + padding: '1px', + }, + debounce= (f, wait= 100)=> { + let timeout; + return (...args)=> { + clearTimeout(timeout); + timeout= setTimeout(()=> f(...args), wait); + }; + }, + thumbnailUrl= (key, size= 720)=> `http://content.jwplatform.com/thumbs/${key}-${size}.jpg`, + createHoc= F=> C=> props=> el(C, {...props, ...F(props)}); + +class JwSelector extends Component { + constructor(...args) { + super(...args); + this.aborters= {}; + this.bindListNode= this.bindListNode.bind(this); + this.onChange= this.onChange.bind(this); + this.onKeyDown= this.onKeyDown.bind(this); + this.updateSuggestions= debounce(this.updateSuggestions.bind(this), 333); + this.state= { + currentSearch: undefined, + input: '', + jwSearch: {}, + selectedSuggestion: undefined, + showSuggestions: false, + }; + this.suggestionNodes= []; + } + + bindListNode(ref) { + this.listNode= ref; + } + + bindSuggestionNodeFor(index) { + return ref=> { + this.suggestionNodes[index]= ref; + } + } + + onChange(event) { + const input= event.target.value; + this.setState({input}, ()=> this.updateSuggestions()); + } + + updateSuggestions() { + const {input}= this.state, /* typed seach text */ + itMatters= 1 itMatters && this.search(input) + ) + } + + search(text) { + const {currentSearch}= this.state, + queryParams= { + text, + action: 'jwp_api_proxy', + method: '/videos/list', + order_by: 'date:desc', + random: Math.random(), + result_limit: this.props.limit || 5, + token: document.getElementsByName('_wpnonce-widget')[0].value, + }; + if (this.aborter) + this.aborter.abort(); + const controller= this.aborter= new AbortController(); + return fetch(addQueryArgs('/wp-admin/admin-ajax.php', queryParams), {signal: controller.signal}) + .then(response=> response.json()) + .then(jwSearch=> this.setState({ + jwSearch, + loading: currentSearch != this.state.currentSearch + })).catch(()=> this.setState({ + loading: currentSearch != this.state.currentSearch + })) + } + + componentDidUpdate(){} + + componentWillUnmount(){ + if (this.aborter) + this.aborter.abort(); + } + + onKeyDown(event) { + const {showSuggestions, selectedSuggestion, jwSearch, loading}= this.state; + if (!showSuggestions || loading || 'ok' !== jwSearch.status || !jwSearch.videos.length) + return; + const vids= jwSearch.videos, + N= vids.length; + event.stopPropagation(); + event.preventDefault(); + switch(event.keyCode) { + case UP: + this.setState({ + selectedSuggestion: (selectedSuggestion || N)-1 + }); + break; + case DOWN: + this.setState({ + selectedSuggestion: ((x=-1)=> (x+1)%N)(selectedSuggestion) + }); + break; + case ENTER: + if (undefined !== selectedSuggestion) { + this.selectVideo(vids[selectedSuggestion]) + } + } + } + + selectVideo(vid) { + this.props.onVideoSelect(vid); + this.setState({ + selectedSuggestion: undefined, + }); + /* todo: select user's input in browser DOM, so + * that it's trivial to type (and search for) something + * different (or to clear the search) */ + } + + renderSelectedVideos() { + const videos= lodash.get(this, 'props.videos'); + if (videos) { + return el('ul', null, + ...videos.map((vid, i)=> + el('li', {key:vid.key, style: videoListStyle}, + el('img', {src: thumbnailUrl(vid.key, 40), style: thumbnailStyle}), + el('span', {style: {flex: 1}}, + vid.title + ), + el('span', null + , 0!==i + ?el(IconButton, { + style: {display: 'inline-flex', padding: '8px 2px', textAlign: 'center'}, + icon: 'arrow-up-alt2', + onClick: ()=> { + this.props.videos.splice(i-1, 0, this.props.videos.splice(i, 1)[0]); + this.props.onChange(this.props.videos); + this.setState({rerender: new Date().getTime()}); + } + }) + :null + , this.props.videos.length-1 !== i + ?el(IconButton, { + style: {display: 'inline-flex', padding: '8px 2px', textAlign: 'center'}, + icon: 'arrow-down-alt2', + onClick: ()=> { + this.props.videos.splice(i+1, 0, this.props.videos.splice(i, 1)[0]); + this.props.onChange(this.props.videos); + this.setState({rerender: new Date().getTime()}); + } + }) + :null + , el(IconButton, { + style: {display: 'inline-flex', textAlign: 'center'}, + icon: 'no', + onClick: ()=> { + this.props.videos.splice(i, 1); + this.props.onChange(this.props.videos); + this.setState({rerender: new Date().getTime()}); + } + }) + ) + ) + ) + ); + } + } + + render() { + const { autoFocus= true, instanceId, limit }= this.props; + const { showSuggestions, videos= lodash.get(this, 'state.jwSearch.videos'), selectedSuggestion, loading, input }= this.state; + const inputDisabled= !!limit && limit <= this.props.videos.length; + return el(Fragment, null, + this.renderSelectedVideos(), + el('div', {className: 'jw-videoselector'}, + el('input', { + 'aria-activedescendant': selectedSuggestion &&`${idPfx}-${instanceId}-${selectedSuggestion}`, + 'aria-autocomplete': 'list', + 'aria-expanded': showSuggestions, + 'aria-label': 'URL', + 'aria-owns': `${idPfx}-${instanceId}`, + autoFocus: autoFocus, + className: idPfx, + disabled: inputDisabled, + onChange: this.onChange, + onInput: event=> event.stopPropagation(), + onKeyDown: this.onKeyDown, + placeholder: inputDisabled ?`Limited to ${limit} videos` :'Type video name', + role: 'combobox', + style: {width: '100%'}, + type: 'text', + value: input, + }), + loading && el('div', null, el(Spinner), 'searching JwPlayer for ', el('i', null, input)) + ), + showSuggestions && videos && !!videos.length && + el(Popover, {position: 'bottom', noArrow: 'noArrow', focusOnMount: false}, + el('div', { + className: `${idPfx}-input-suggestions`, + id: `${idPfx}-input-suggestions-${instanceId}`, + ref: this.bindListNode, + role: 'listbox', + }, videos.map((vid, i)=> + el('button', { + 'aria-selected': i===selectedSuggestion, + className: `${idPfx}-input-suggestion`+(i===selectedSuggestion ?' is-selected' :''), + id: `${idPfx}-${instanceId}-${i}`, + key: vid.key, + onClick: ()=> this.selectVideo(vid), + ref: this.bindSuggestionNodeFor(i), + role: 'option', + tabIndex: -1, + }, + el('div', {style: {display: 'flex', alignItems: 'center'}}, + el('img', {src: thumbnailUrl(vid.key, 40), style: thumbnailStyle}), + el('div', null, + vid.title || '(no title)' + ) + ) + ) + )) + ) + ) + } +} + +export default withInstanceId(JwSelector); From 0d5ecd647acf6685b72b0714e10513dfec623166 Mon Sep 17 00:00:00 2001 From: Raul Miller Date: Tue, 18 Feb 2020 12:17:06 -0500 Subject: [PATCH 4/5] simple gutenberg block support --- jw-player/static/js/jwplayer-embed.js | 44 ++--- jw-player/static/js/jwplayer-selector.js | 233 ++++++----------------- 2 files changed, 83 insertions(+), 194 deletions(-) diff --git a/jw-player/static/js/jwplayer-embed.js b/jw-player/static/js/jwplayer-embed.js index 70fbc90..fa5a697 100644 --- a/jw-player/static/js/jwplayer-embed.js +++ b/jw-player/static/js/jwplayer-embed.js @@ -1,7 +1,4 @@ let VideoSelector; -(async()=> { - VideoSelector= (await import('./jwplayer-selector.js')).default; -})(); (()=> { const {registerBlockType}= wp.blocks, @@ -9,7 +6,7 @@ let VideoSelector; {createElement, Component}= wp.element, el= createElement; registerBlockType( - 'jwplayer-embed-block/jwplayer-embed', { + 'jwplayer-block/jwplayer-embed', { title: 'JWPlayer Video', icon: el(SVG, { alt: 'JWPlayer', @@ -27,45 +24,48 @@ let VideoSelector; ), category: 'embed', attributes: { - video: { + key: { type: 'string', - } + }, }, edit: class JwBlockEdit extends Component { constructor(...args) { super(...args); this.setVideo= this.setVideo.bind(this); - this.video= undefined; } - setVideo(v) { - this.props.setAttributes({video: v.key}); + if (!v) + return console.log('what you talkin about, willis?'); + this.props.setAttributes({key: `${v.key}`}); + } + componentDidMount() { + /* import jw selector here, so we can guarantee a subsequent paint event */ + const {key}= this.props.attributes; + const paint= ()=> this.forceUpdate(); + import('./jwplayer-selector.js').then(imp=> { + VideoSelector= imp.default; + paint(); + }); } - render() { - const {video}= this.props.attributes; + const {key}= this.props.attributes; return el( 'div', null, - video && el('img', {src: `http://content.jwplatform.com/thumbs/${video}-40.jpg`}), - video ? el('div', null, `[jwplayer ${video}]`) + key && el('img', {src: `http://content.jwplatform.com/thumbs/${key}-40.jpg`}), + key ? el('div', null, `[jwplayer ${key}]`) : VideoSelector - ? el(VideoSelector, {videos: [], onVideoSelect: this.setVideo}) + ? el(VideoSelector, {onVideoSelect: this.setVideo}) : el('div', null, - el(Spinner), '... loading VideoSelector') + el(Spinner), '... loading JW VideoSelector') ) } }, save: class JwBlockSave extends Component { render (props) { -console.log('SAVE',this,arguments); - const {video}= this.props.attributes; - return el('div', null, video ? `[jwplayer ${video}]` :null); + const {key}= this.props.attributes; + return el('div', null, key ? `[jwplayer ${key}]` :null); } }, - attributes: { - shortcode: { - }, - }, } ); })(); diff --git a/jw-player/static/js/jwplayer-selector.js b/jw-player/static/js/jwplayer-selector.js index b094c0b..9eb9451 100644 --- a/jw-player/static/js/jwplayer-selector.js +++ b/jw-player/static/js/jwplayer-selector.js @@ -1,34 +1,17 @@ -/* - * currently overblown: - * -- includes code for managing a list of selected videos - * but only used to select a single video, so that list is never visible (and poorly tested) */ const { IconButton, Popover, Spinner } = wp.components, { withInstanceId } = wp.compose, - { withSelect } = wp.data, - { createElement, Component, Fragment } = wp.element, - { decodeEntities } = wp.htmlEntities, - { UP, DOWN, ENTER } = wp.keycodes, + { createElement, Component } = wp.element, { addQueryArgs } = wp.url, el= createElement, - idPfx= 'jwvideo-selector', - thumbnailStyle = { + idPfx= 'jwvideo-selector', + thumbnailStyle = { borderRadius: '3px', height:'50px', margin: '2px', overflow: 'hidden', width: '50px', }, - videoListStyle = { - alignItems: 'center', - background: '#f9f9f9', - border: '1px solid #ccc', - borderRadius: '3px', - display: 'flex', - flexWrap: 'nowrap', - justifyContent: 'flex-start', - marginBottom: '3px', - padding: '1px', - }, + thumbnailUrl= (key, size= 720)=> `http://content.jwplatform.com/thumbs/${key}-${size}.jpg`, debounce= (f, wait= 100)=> { let timeout; return (...args)=> { @@ -36,23 +19,20 @@ const { IconButton, Popover, Spinner } = wp.components, timeout= setTimeout(()=> f(...args), wait); }; }, - thumbnailUrl= (key, size= 720)=> `http://content.jwplatform.com/thumbs/${key}-${size}.jpg`, - createHoc= F=> C=> props=> el(C, {...props, ...F(props)}); + createHoc= F=> C=> props=> el(C, {...props, ...F(props)}); /* TODO? refactor search method as hook and introduce to JwSelector as hoc */ -class JwSelector extends Component { +export default class JwSelector extends Component { constructor(...args) { super(...args); this.aborters= {}; this.bindListNode= this.bindListNode.bind(this); + this.instanceId= new Date().getTime(); this.onChange= this.onChange.bind(this); - this.onKeyDown= this.onKeyDown.bind(this); - this.updateSuggestions= debounce(this.updateSuggestions.bind(this), 333); this.state= { currentSearch: undefined, input: '', - jwSearch: {}, - selectedSuggestion: undefined, - showSuggestions: false, + jwSearch: {videos: [], rate_limit: {remaining: true}}, + warning: '', }; this.suggestionNodes= []; } @@ -61,28 +41,17 @@ class JwSelector extends Component { this.listNode= ref; } - bindSuggestionNodeFor(index) { - return ref=> { - this.suggestionNodes[index]= ref; - } - } - onChange(event) { const input= event.target.value; - this.setState({input}, ()=> this.updateSuggestions()); - } - - updateSuggestions() { - const {input}= this.state, /* typed seach text */ - itMatters= 1 { + const searchId= new Date().getTime(); + this.setState({ currentSearch: searchId, - showSuggestions: itMatters, - selectedSuggestion: undefined, - loading: itMatters, - }, () => itMatters && this.search(input) - ) + loading: 1 < input.length, + warning: '', + }, ()=> 1 (x+1)%N)(selectedSuggestion) - }); - break; - case ENTER: - if (undefined !== selectedSuggestion) { - this.selectVideo(vids[selectedSuggestion]) - } - } - } - selectVideo(vid) { this.props.onVideoSelect(vid); this.setState({ - selectedSuggestion: undefined, + input: '', }); - /* todo: select user's input in browser DOM, so - * that it's trivial to type (and search for) something - * different (or to clear the search) */ - } - - renderSelectedVideos() { - const videos= lodash.get(this, 'props.videos'); - if (videos) { - return el('ul', null, - ...videos.map((vid, i)=> - el('li', {key:vid.key, style: videoListStyle}, - el('img', {src: thumbnailUrl(vid.key, 40), style: thumbnailStyle}), - el('span', {style: {flex: 1}}, - vid.title - ), - el('span', null - , 0!==i - ?el(IconButton, { - style: {display: 'inline-flex', padding: '8px 2px', textAlign: 'center'}, - icon: 'arrow-up-alt2', - onClick: ()=> { - this.props.videos.splice(i-1, 0, this.props.videos.splice(i, 1)[0]); - this.props.onChange(this.props.videos); - this.setState({rerender: new Date().getTime()}); - } - }) - :null - , this.props.videos.length-1 !== i - ?el(IconButton, { - style: {display: 'inline-flex', padding: '8px 2px', textAlign: 'center'}, - icon: 'arrow-down-alt2', - onClick: ()=> { - this.props.videos.splice(i+1, 0, this.props.videos.splice(i, 1)[0]); - this.props.onChange(this.props.videos); - this.setState({rerender: new Date().getTime()}); - } - }) - :null - , el(IconButton, { - style: {display: 'inline-flex', textAlign: 'center'}, - icon: 'no', - onClick: ()=> { - this.props.videos.splice(i, 1); - this.props.onChange(this.props.videos); - this.setState({rerender: new Date().getTime()}); - } - }) - ) - ) - ) - ); - } } render() { - const { autoFocus= true, instanceId, limit }= this.props; - const { showSuggestions, videos= lodash.get(this, 'state.jwSearch.videos'), selectedSuggestion, loading, input }= this.state; - const inputDisabled= !!limit && limit <= this.props.videos.length; - return el(Fragment, null, - this.renderSelectedVideos(), - el('div', {className: 'jw-videoselector'}, + const { instanceId, props }= this; + const { autoFocus= true }= props; + const { input, jwSearch, loading, warning }= this.state; + const { videos }= jwSearch; + return el('div', {className: 'jw-videoselector'}, el('input', { - 'aria-activedescendant': selectedSuggestion &&`${idPfx}-${instanceId}-${selectedSuggestion}`, - 'aria-autocomplete': 'list', - 'aria-expanded': showSuggestions, - 'aria-label': 'URL', - 'aria-owns': `${idPfx}-${instanceId}`, autoFocus: autoFocus, className: idPfx, - disabled: inputDisabled, onChange: this.onChange, onInput: event=> event.stopPropagation(), - onKeyDown: this.onKeyDown, - placeholder: inputDisabled ?`Limited to ${limit} videos` :'Type video name', + placeholder: 'Type video name', role: 'combobox', style: {width: '100%'}, type: 'text', value: input, }), - loading && el('div', null, el(Spinner), 'searching JwPlayer for ', el('i', null, input)) - ), - showSuggestions && videos && !!videos.length && - el(Popover, {position: 'bottom', noArrow: 'noArrow', focusOnMount: false}, - el('div', { - className: `${idPfx}-input-suggestions`, - id: `${idPfx}-input-suggestions-${instanceId}`, - ref: this.bindListNode, - role: 'listbox', - }, videos.map((vid, i)=> - el('button', { - 'aria-selected': i===selectedSuggestion, - className: `${idPfx}-input-suggestion`+(i===selectedSuggestion ?' is-selected' :''), - id: `${idPfx}-${instanceId}-${i}`, - key: vid.key, - onClick: ()=> this.selectVideo(vid), - ref: this.bindSuggestionNodeFor(i), - role: 'option', - tabIndex: -1, - }, - el('div', {style: {display: 'flex', alignItems: 'center'}}, - el('img', {src: thumbnailUrl(vid.key, 40), style: thumbnailStyle}), - el('div', null, - vid.title || '(no title)' + warning ?warning + :loading + ?el('div', null, el(Spinner), 'searching JwPlayer for ', el('i', null, input)) + :el(Popover, {position: 'bottom', noArrow: 'noArrow', focusOnMount: false}, + el('div', { + className: `${idPfx}-input-suggestions`, + id: `${idPfx}-input-suggestions-${instanceId}`, + ref: this.bindListNode, + role: 'listbox', + }, videos.map((vid, i)=> + el('button', { + className: `${idPfx}-input-suggestion`, + id: `${idPfx}-${instanceId}-${i}`, + key: vid.key, + onClick: ()=> this.selectVideo(vid), + role: 'option', + tabIndex: -1, + }, + el('div', {style: {display: 'flex', alignItems: 'center'}}, + el('img', {src: thumbnailUrl(vid.key, 40), style: thumbnailStyle}), + el('div', null, + vid.title || '(no title)' + ) + ) ) - ) + )) ) - )) - ) - ) + ) + } } -export default withInstanceId(JwSelector); From 9587362b8bab19f2ac780296dc4948c0f9f3ca30 Mon Sep 17 00:00:00 2001 From: Raul Miller Date: Fri, 21 Feb 2020 11:44:05 -0500 Subject: [PATCH 5/5] fix debounce of jw search --- jw-player/static/js/jwplayer-selector.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/jw-player/static/js/jwplayer-selector.js b/jw-player/static/js/jwplayer-selector.js index 9eb9451..3d13b24 100644 --- a/jw-player/static/js/jwplayer-selector.js +++ b/jw-player/static/js/jwplayer-selector.js @@ -15,6 +15,7 @@ const { IconButton, Popover, Spinner } = wp.components, debounce= (f, wait= 100)=> { let timeout; return (...args)=> { + const id= new Date().getTime(); clearTimeout(timeout); timeout= setTimeout(()=> f(...args), wait); }; @@ -26,6 +27,7 @@ export default class JwSelector extends Component { super(...args); this.aborters= {}; this.bindListNode= this.bindListNode.bind(this); + this.search= debounce(this.search.bind(this), 1000); this.instanceId= new Date().getTime(); this.onChange= this.onChange.bind(this); this.state= { @@ -43,17 +45,16 @@ export default class JwSelector extends Component { onChange(event) { const input= event.target.value; - this.setState({input}, debounce(()=> { - const searchId= new Date().getTime(); + this.setState({input}, ()=> this.setState({ - currentSearch: searchId, + currentSearch: new Date().getTime(), loading: 1 < input.length, warning: '', }, ()=> 1 event.stopPropagation(), placeholder: 'Type video name', @@ -126,7 +127,7 @@ export default class JwSelector extends Component { id: `${idPfx}-input-suggestions-${instanceId}`, ref: this.bindListNode, role: 'listbox', - }, videos.map((vid, i)=> + }, videos && videos.map((vid, i)=> el('button', { className: `${idPfx}-input-suggestion`, id: `${idPfx}-${instanceId}-${i}`,