PluginProbe
FV Player 8 / trunk
FV Player 8 vtrunk
trunk 8.0.18 8.0.19 8.0.20 8.0.21 8.0.25 8.0.27 8.1 8.1.3
fv-player / js / shortcode-editor.js

shortcode-editor.js in FV Player 8 trunk, at js/shortcode-editor.js

5,286 lines 188.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* eslint-disable no-global-assign */
2 /*global fvwpflowplayer_helper_tag, fv_wp_flowplayer_re_edit, fv_wp_flowplayer_re_insert, fv_flowplayer_set_post_thumbnail_id, fv_flowplayer_set_post_thumbnail_nonce, */
3 /*global FCKeditorAPI, setPostThumbnailL10n, send_to_editor, tinymce*/
4
5 jQuery(function() {
6 // The actual editor
7 window.fv_player_editor = (function($) {
8 var
9 $doc = $(document),
10 $el_editor = $('#fv-player-shortcode-editor'),
11 $el_preview = $('#fv-player-shortcode-editor-preview'),
12 el_spinner = $('#fv-player-shortcode-editor-preview-spinner'),
13 el_preview_target = $('#fv-player-shortcode-editor-preview-target'),
14 $el_notices = $('#fv-player-editor-notices'),
15
16 // data to save in Ajax
17 ajax_save_this_please = false,
18
19 // last saved data to detect changes for auto-saving
20 ajax_save_previous = false,
21
22 current_player_db_id = 0,
23 current_player_object = false,
24 current_video_db_id = 0,
25 current_video_to_edit = -1,
26
27 current_editor_tab = false,
28
29 // Used for Video Custom Fields
30 current_post_id = 0,
31 current_post_meta_key = 0,
32
33 deleted_videos = [],
34 deleted_video_meta = [],
35 deleted_player_meta = [],
36
37 prevent_reload_for_current_save = false,
38
39 // stores the button which was clicked to open the editor
40 editor_button_clicked = 0,
41
42 // the post editor content being edited
43 editor_content,
44
45 // used in WP Heartbeat
46 edit_lock_removal = {},
47
48 // CodeMirror instance, if any
49 instance_code_mirror,
50
51 // keep track of the last cursor position in CodeMirror
52 instance_code_mirror_cursor_last,
53
54 // TinyMCE instance, if any
55 instance_tinymce,
56
57 // Foliopress WYSIWYG instance, if any
58 instance_fp_wysiwyg,
59
60 is_loading = false,
61
62 // are we editing player which is not yet in DB?
63 is_unsaved = true,
64
65 is_playlist_hero_editing = false,
66
67 // is the player already saved in the DB but actually
68 // still in a "draft" status? i.e. not published yet
69 has_draft_status = true,
70
71 // will be > 0 when any meta data are loading that need saving along with the form (example: S3 video duration, PPV product creation)
72 // ... this prevents overlay closing until all meta required data are loaded and stored
73 is_loading_meta_data = 0,
74
75 // whether we're editing a single video (true) or showing a playlist view (false)
76 editing_video_details = false,
77
78 // are we currently saving data?
79 is_saving = false,
80
81 fv_player_shortcode_editor_ajax,
82
83 // used when editing shortcode in TinyMCE
84 helper_tag = window.fvwpflowplayer_helper_tag,
85
86 // the part of shortcode outside of [fvplayer id="XYZ"]
87 // also accessed from outside
88 shortcode_remains,
89
90 // Some shortcode args should be kept. For example if you edit
91 // [fvplayer id="1" sort="newest"] that sort should not be removed
92 store_shortcode_args = {},
93
94 // Some shortcode args do not have a DB counterpars, so they should always
95 // be kept on the shortcode. For example if you edit
96 // [fvplayer src="some_video_url" sort="newest"] that sort should not be removed,
97 // as it's not been transferred into the DB
98 always_keep_shortcode_args = {},
99
100 // Flowplayer only lets us specify the RTMP server for the first video in plalist, so we store it here when the playlist item order is changing etc.
101 store_rtmp_server = '',
102
103 // stores parts of editor HTML which are later re-used when adding new items
104 template_playlist_item,
105 template_video,
106 template_subtitles_tab,
107
108 // used to remember which widget we are editing, if any
109 widget_id,
110
111 is_editing_playlist_item_title = false,
112
113 // list of errors that currently prevent auto-saving in the form of: { error_identifier_with_(plugin_)prefix : "the actual error text to show" }
114 // ... this will be shown in place of the "Saved!" message bottom overlay and it will always show only the first error in this object,
115 // as to not overload the user and UI with errors. Once that error is corrected, it gets removed from this object and next one (if any) is shown.
116 errors = {},
117
118 store_debug_log = [];
119
120 debug_log('Loading...');
121
122 /**
123 * Adds a notice at the bottom of player. Used for successful save and save errors. Notices are removed on successful save.
124 *
125 * @param {string} id
126 * @param {string} msg
127 * @param {int} [timeout] Duration for which it should appear or omit for persistent message
128 */
129 function add_notice( id, msg, timeout ) {
130 var notice = $('<div class="fv-player-editor-notice fv-player-editor-notice_'+id+'">'+msg+'</div>' );
131 $el_notices.append( notice );
132
133 if( typeof(timeout) == 'number' ) {
134 setTimeout( function() {
135 notice.fadeOut();
136 }, timeout );
137 }
138 }
139
140 function add_shortcode_args( args ) {
141 let
142 params = jQuery.map( args, function (value, index) {
143 return index + '="' + value + '"';
144 });
145
146 return params.length ? ' ' + params.join(' ') : '';
147 }
148
149 function copy_player_button_toggle( show ) {
150 $('#fv-player-shortcode-editor .copy_player').toggle( show );
151 }
152
153 function check_for_player_meta_field(fieldName) {
154 return get_field_conf( fieldName ).store == 'player_meta';
155 }
156
157 function check_for_video_meta_field(fieldName) {
158
159 if ( get_field_conf( fieldName ).store == 'video_meta' ) {
160 return true;
161 }
162
163 return [
164 'fv_wp_flowplayer_field_duration',
165 'fv_wp_flowplayer_field_dvr',
166 'fv_wp_flowplayer_field_auto_splash',
167 'fv_wp_flowplayer_field_auto_caption',
168 'fv_wp_flowplayer_field_audio'
169 ].indexOf(fieldName) > -1;
170 }
171
172 function get_current_player_db_id() {
173 return current_player_db_id;
174 }
175
176 function get_current_player_object() {
177 return current_player_object;
178 }
179
180 function get_current_video_meta( key ) {
181 var video_object = get_current_video_object(),
182 video_meta = false;
183
184 if( video_object ) {
185 $( video_object.meta ).each( function(k,v) {
186 if( v.meta_key == key ) {
187 video_meta = v;
188 return false;
189 }
190 } );
191 }
192 return video_meta;
193 }
194
195 function get_current_video_meta_value( key ) {
196 var video_object = get_current_video_object(),
197 video_id = video_object && video_object.id ? video_object.id : -1,
198 video_meta = get_current_video_meta( key );
199
200 if( video_meta ) {
201 debug_log( "Video meta '"+key+"' for #"+video_id, video_meta.meta_value );
202 return video_meta.meta_value;
203 }
204 debug_log( "Video meta '"+key+"' not found for #"+video_id );
205 return false;
206 }
207
208 function get_current_video_object() {
209 if( current_video_to_edit == -1 ) return false;
210
211 if( current_player_object.videos && current_player_object.videos[current_video_to_edit] ) {
212 return current_player_object.videos[current_video_to_edit];
213 }
214 return false;
215 }
216
217 /*
218 * Used when entering link to the "hero" field.
219 * If it's used in the playlist mode, we need to insert the hidden new playlist row and use the added field.
220 */
221 function get_hero_src_field( input ) {
222 let editor_src_field = get_field("src")
223
224 if( $( '#playlist-hero:visible' ).length && input.data('playlist-hero') ) {
225
226 // Remember the new item index to not add new video on each keyup event!
227 if( !input.data( 'new-item-index' ) ) {
228 input.data( 'new-item-index', get_playlist_items_count() );
229 }
230
231 editor_src_field = editor_src_field.eq( input.data( 'new-item-index' ) );
232
233 // Add new playlist item if it's not there
234 if( !editor_src_field.length ) {
235 let new_item = playlist_item_add();
236
237 // If we are going to type in the URL, do not show the row
238 if( input.attr( 'type') == 'text' ) {
239 $( '#fv-player-editor-playlist .fv-player-editor-playlist-item:last').hide();
240 }
241
242 editor_src_field = get_field( "src", new_item.video_tab );
243 }
244 }
245
246 return editor_src_field;
247 }
248
249 /*
250 * Returns the number of videos in a playlist for the current player.
251 */
252 function get_playlist_items_count() {
253 // TODO: Could we just use current_player_object.videos.length ? Would that work with editing?
254 return jQuery('#fv-player-editor-playlist .fv-player-editor-playlist-item').length;
255 }
256
257 function get_playlist_video_meta( meta_key, index ) {
258 var video_object = current_player_object.videos && current_player_object.videos[index],
259 video_meta = false;
260
261 if( video_object ) {
262 $( video_object.meta ).each( function(k,v) {
263 if( v.meta_key == meta_key ) {
264 video_meta = v;
265 return false;
266 }
267 } );
268 }
269 return video_meta;
270 }
271
272 function get_playlist_video_object( index ) {
273 if( current_player_object.videos && current_player_object.videos[index] ) {
274 return current_player_object.videos[index]
275 }
276 return false;
277 }
278
279 function get_playlist_video_meta_value( meta_key, index ) {
280 var video_meta = get_playlist_video_meta( meta_key, index );
281
282 if( video_meta ) {
283 return video_meta.meta_value;
284 }
285
286 return false;
287 }
288
289 /**
290 * A shorthand to save you from all the "fv_wp_flowplayer_field_"
291 * when selecting fields
292 *
293 * @param {string} key The field key. For example "src" gives
294 * you "fv_wp_flowplayer_field_src"
295 * @param {bool|jQuery|string} where Lets you narrow down the element wher you
296 * want to locate he field. You can use a jQuery
297 * element or a string selector for jQuery.
298 * Or TRUE to get the currently open playlist item.
299 *
300 * @return {jQuery} The field element/elements (in case of multiple language subtitles)
301 */
302 function get_field( key, where ) {
303 var element = false,
304 selector = '.' + map_names_to_editor_fields(key) + ', [name=' + map_names_to_editor_fields(key) + ']';
305
306 if( typeof(where) == 'boolean' && where ) {
307 where = jQuery('.fv-player-tab [data-index='+current_video_to_edit+']');
308 element = where.find(selector);
309
310 } else if( where && typeof(where) == "object" ) {
311 element = where.find(selector);
312 } else if( where && typeof(where) == "string" ) {
313 element = $el_editor.find(where).find(selector);
314 } else {
315 element = $el_editor.find(selector);
316 }
317
318 if( !element.length ) {
319 console.log('FV Player Editor Warning: field '+key+' not found');
320 }
321
322 return element;
323 }
324
325 function get_field_conf( name ) {
326 name = get_field_name( name );
327
328 if ( window.fv_player_editor_fields && window.fv_player_editor_fields[ name ] ) {
329 return window.fv_player_editor_fields[ name ];
330 }
331
332 return {};
333 }
334
335 /**
336 * Gives you "src" out of "fv_wp_flowplayer_field_src"
337 *
338 * @param {string} name The field name
339 *
340 * @return {string} The field.... real name?
341 */
342 function get_field_name( name ) {
343 if (name.indexOf('fv_wp_flowplayer_field_') > -1) {
344 return name.replace('fv_wp_flowplayer_field_', '');
345 }
346 return name;
347 }
348
349 /**
350 * Gives you the desired tab with video information
351 *
352 * @param {int|string} index Number, or first, or last
353 * @param {string} tab Tab name to obtain, options:
354 * * video-files
355 * * subtitles
356 *
357 * @return {object} The tab element
358 */
359 function get_tab( index, tab ) {
360 var selector = '.fv-player-tab-'+tab+' [data-playlist-item]';
361 if( index == 'first' ) {
362 selector += ':first';
363 } else if( index == 'last' ) {
364 selector += ':last';
365 } else {
366 selector += '[data-index='+index+']';
367 }
368 return $el_editor.find(selector);
369 }
370
371 /**
372 * Gives you all desired tabs of a certain kind
373 *
374 * @return {object} The tab elements
375 */
376 function get_tabs( tab ) {
377 var selector = '.fv-player-tab-'+tab+' [data-playlist-item]';
378 return $el_editor.find(selector);
379 }
380
381 function has_errors() {
382 for ( var i in errors ) {
383 return errors[ i ];
384 }
385
386 return false;
387 }
388
389 function hide_inputs() {
390 if( fv_player_editor_conf.hide ) {
391 $.each( fv_player_editor_conf.hide, function( k, v ) {
392 try {
393 if( 'configure-video' === v ) {
394 $( 'a.configure-video' ).hide();
395 $( "<style>.fv-player-editor-playlist-item .fvp_item_video-thumbnail { cursor: default; }</style>" ).appendTo( "head" )
396
397 } else {
398 $( '.fv-player-editor-field-wrap-'+v ).hide();
399 $( '[name=fv_wp_flowplayer_field_'+v+']' ).hide();
400 }
401 } catch(e) {
402 debug_log( 'Hide Field: Failure', e );
403 }
404 } );
405 }
406 }
407
408 /**
409 * Automatically handles new, updated or removed player meta data
410 * via JS.
411 *
412 * @param options Object with the following elements:
413 * element -> the actual element on page (input, select...) to check and get meta value from
414 * data -> existing player data, including player meta data
415 * meta_section -> section for the meta data, for example "player" for common metas, "ppv" for pay per view plugin etc.
416 * meta_key -> rhe actual key to check for and potentially add/update/remove
417 * handle_delete -> if true, value-less ('') elements will be considered indication that the meta key should be deleted
418 * delete_callback -> if set, this function is called when a meta key is deleted
419 * edit_callback -> if set, this function is called when a meta key is updated
420 * insert_callback -> if set, this function is called when a meta key is added
421 */
422 function insertUpdateOrDeletePlayerMeta(options) {
423 var
424 $element = jQuery(options.element),
425 value = map_input_value( $element );
426
427 if ( ! options.meta_section ) {
428 options.meta_section = 'player';
429 }
430
431 // don't do anything if we've not found the actual element
432 if (!$element.length) {
433 return;
434 }
435
436 // check whether to update or delete this meta
437 if ($element.data('id')) {
438 // only delete this meta if delete was not prevented via options
439 // and if there was no value specified, otherwise update
440 if ((!options.handle_delete || options.handle_delete !== false) && !value) {
441 deleted_player_meta.push( $element.data('id') );
442
443 $element
444 .removeData('id')
445 .removeAttr('data-id');
446
447 // execute delete callback, if present
448 if (options.delete_callback && typeof(options.delete_callback) == 'function') {
449 options.delete_callback();
450 }
451 } else {
452 if (typeof(options.data) != 'undefined' && typeof(options.data['player_meta'][options.meta_section]) == 'undefined') {
453 options.data['player_meta'][options.meta_section] = {};
454 }
455
456 // update if we have an ID
457 if (typeof(options.data) != 'undefined') {
458 options.data['player_meta'][options.meta_section][options.meta_key] = {
459 'id': $element.data('id'),
460 'value': value
461 }
462 }
463
464 // execute update callback, if present
465 if (options.edit_callback && typeof(options.edit_callback) == 'function') {
466 options.edit_callback();
467 }
468 }
469 } else if (value || options.handle_delete !== false ) {
470 if (typeof(options.data) != 'undefined' && typeof(options.data['player_meta'][options.meta_section]) == 'undefined') {
471 options.data['player_meta'][options.meta_section] = {};
472 }
473
474 // insert new data if no meta ID
475 if (typeof(options.data) != 'undefined') {
476 options.data['player_meta'][options.meta_section][options.meta_key] = {
477 'value': value
478 }
479 }
480
481 if ( value ) {
482 // execute insert callback, if present
483 if (options.insert_callback && typeof(options.insert_callback) == 'function') {
484 options.insert_callback();
485 }
486
487 } else {
488 if (options.delete_callback && typeof(options.delete_callback) == 'function') {
489 options.delete_callback();
490 }
491 }
492
493 }
494 }
495
496 /**
497 * Automatically handles new, updated or removed video meta data
498 * via JS.
499 *
500 * @param options Object with the following elements:
501 * element -> the actual element on page (input, select...) to check and get meta value from
502 * data -> existing player data, including video meta data
503 * meta_section -> section for the meta data, for example "player" for common metas, "ppv" for pay per view plugin etc.
504 * meta_key -> rhe actual key to check for and potentially add/update/remove
505 * handle_delete -> if true, value-less ('') elements will be considered indication that the meta key should be deleted
506 * delete_callback -> if set, this function is called when a meta key is deleted
507 * edit_callback -> if set, this function is called when a meta key is updated
508 * insert_callback -> if set, this function is called when a meta key is added
509 */
510 function insertUpdateOrDeleteVideoMeta(options) {
511 var
512 $element = jQuery(options.element),
513 value = map_input_value( $element );
514
515 // don't do anything if we've not found the actual element
516 if (!$element.length) {
517 return;
518 }
519
520 // check whether to update or delete this meta
521 if ($element.data('id')) {
522 // only delete this meta if delete was not prevented via options
523 // and if there was no value specified, otherwise update
524 if ((!options.handle_delete || options.handle_delete !== false) && !$element.val()) {
525 deleted_video_meta.push( $element.data('id') );
526
527 $element
528 .removeData('id')
529 .removeAttr('data-id');
530
531 // execute delete callback, if present
532 if (options.delete_callback && typeof(options.delete_callback) == 'function') {
533 options.delete_callback();
534 }
535 } else {
536 if (typeof(options.data) != 'undefined' && typeof(options.data['video_meta'][options.meta_section]) == 'undefined') {
537 options.data['video_meta'][options.meta_section] = {};
538 }
539
540 if (typeof(options.data) != 'undefined') {
541 // update if we have an ID
542 options.data['video_meta'][options.meta_section][options.meta_index][options.meta_key] = {
543 'id': $element.data('id'),
544 'value': value
545 }
546
547 // execute update callback, if present
548 if (options.edit_callback && typeof(options.edit_callback) == 'function') {
549 options.edit_callback();
550 }
551 }
552 }
553 } else if (value) {
554 if (typeof(options.data) != 'undefined' && typeof(options.data['video_meta'][options.meta_section]) == 'undefined') {
555 options.data['video_meta'][options.meta_section] = {};
556 }
557
558 // insert new data if no meta ID
559 if (typeof(options.data) != 'undefined') {
560 options.data['video_meta'][options.meta_section][options.meta_index][options.meta_key] = {
561 'value': value
562 }
563
564 // execute insert callback, if present
565 if (options.insert_callback && typeof(options.insert_callback) == 'function') {
566 options.insert_callback();
567 }
568 }
569 }
570 }
571
572 $doc.ready( function(){
573 var
574 next = false, // track if the player data has changed while saving
575 overlay_close_waiting_for_save = false,
576 ajax_saving = true,
577 int_keyup = false;
578
579 /*$(window).on('beforeunload', function(e) {
580 if (is_draft && is_draft_changed) {
581 return e.originalEvent.returnValue = 'You have unsaved changes. Are you sure you want to close this dialog and loose them?';
582 }
583 });*/
584
585 if( jQuery().fv_player_box ) {
586 debug_log('Attaching click actions...');
587
588 $doc.on( 'click', '.fv-wordpress-flowplayer-button, .fv-player-editor-button, .fv-player-edit', function(e) {
589 // make the TinyMCE editor below this button active,
590 // as otherwise we would be inserting into the last TinyMCE instance
591 // on the page if no TinyMCE instance was clicked into - which is not what we want to do
592 if (typeof(tinyMCE) != 'undefined' && typeof(tinyMCE.activeEditor) != 'undefined' && this.className.indexOf('fv-wordpress-flowplayer-button') > -1) {
593 var
594 $btn = jQuery(this),
595 $wraper_div = $btn.parents('.wp-editor-wrap:first');
596
597 for (var i in tinyMCE.editors) {
598 if (tinyMCE.editors[i].editorContainer && tinyMCE.editors[i].editorContainer.id && $wraper_div.find('#' + tinyMCE.editors[i].editorContainer.id).length) {
599 tinyMCE.activeEditor = tinyMCE.editors[i];
600 break;
601 }
602 }
603 }
604
605 editor_button_clicked = this;
606 e.preventDefault();
607
608 $.fv_player_box( {
609 onComplete : editor_open,
610 onClosed : editor_close,
611 onOpen: lightbox_open
612 } );
613
614 widget_id = $(this).data().number;
615 });
616
617 /**
618 * Elementor Widget support
619 */
620 if ( window.elementor && window.elementor.channels && window.elementor.channels.editor ) {
621
622 // "Configure Player" button
623 elementor.channels.editor.on( 'fv-player-elementor-editor-open', function(e) {
624 editor_button_clicked = e.el;
625
626 $.fv_player_box( {
627 onComplete : editor_open,
628 onClosed : editor_close,
629 onOpen: lightbox_open
630 } );
631 });
632
633 // "Select Media" button for "Source URL"
634 elementor.channels.editor.on( 'fv-player-elementor-pick-source_url ', function(e) {
635 fv_flowplayer_uploader_button = jQuery( e.el );
636
637 $( '.elementor-control-source_url [data-setting="url"]' ).addClass( 'fv_flowplayer_target' );
638
639 fv_flowplayer_uploader_open();
640 } );
641
642 // "Select Media" button for "Splash URL"
643 elementor.channels.editor.on( 'fv-player-elementor-pick-splash_url ', function(e) {
644 fv_flowplayer_uploader_button = jQuery( e.el );
645
646 $( '.elementor-control-splash_url [data-setting="url"]' ).addClass( 'fv_flowplayer_target' );
647
648 fv_flowplayer_uploader_open();
649 } );
650 }
651
652 /**
653 * Look for buttons in the block editor canvas iframe (post editor and Site Editor).
654 */
655 function getEditorCanvasIframe() {
656 return jQuery('iframe[name="editor-canvas"], iframe.editor-canvas__iframe, iframe.edit-site-visual-editor__editor-canvas');
657 }
658
659 function setupSiteEditorHandlers() {
660 var site_editor_iframe = getEditorCanvasIframe().contents();
661 if( site_editor_iframe.length ) {
662 debug_log( 'Editor canvas iframe found!' );
663
664 // FV Player Editor
665 site_editor_iframe.off( 'click', '.fv-wordpress-flowplayer-button, .fv-player-editor-button, .fv-player-edit' );
666
667 site_editor_iframe.on( 'click', '.fv-wordpress-flowplayer-button, .fv-player-editor-button, .fv-player-edit', function(e) {
668
669 editor_button_clicked = this;
670 e.preventDefault();
671 $.fv_player_box( {
672 top: "100px",
673 initialWidth: 1100,
674 initialHeight: 50,
675 width:"1100px",
676 height:"100px",
677 href: "#fv-player-shortcode-editor",
678 inline: true,
679 title: 'Add FV Player',
680 onComplete : editor_open,
681 onClosed : editor_close,
682 onOpen: lightbox_open
683 } );
684 widget_id = $(this).data().number;
685 });
686
687 // FV Player Block "Select Media" button
688 site_editor_iframe.off( 'click', '.fv-player-gutenberg-media' );
689
690 site_editor_iframe.on( 'click', '.fv-player-gutenberg-media', fv_flowplayer_uploader_init );
691 }
692 }
693
694 // Initial setup with interval
695 var site_editor_load = setInterval( function() {
696 var canvas = getEditorCanvasIframe();
697 if( canvas.length ) {
698 clearInterval(site_editor_load);
699 setupSiteEditorHandlers();
700
701 // Watch for iframe load events (fires when iframe reloads)
702 canvas.on('load', function() {
703 debug_log( 'Editor canvas iframe loaded, reattaching handlers' );
704 setTimeout( setupSiteEditorHandlers, 100 );
705 });
706 }
707 }, 1000 );
708
709 $doc.on( 'click', '.fv-player-export', function(e) {
710 let $element = jQuery(this),
711 player_id = $element.data('player_id');
712
713 e.preventDefault();
714 $.fv_player_box( {
715 onComplete : function() {
716 overlay_show('loading');
717
718 debug_log('Running fv_player_db_export Ajax for #' + player_id );
719
720 $.post(ajaxurl, {
721 action: 'fv_player_db_export',
722 playerID : player_id,
723 nonce : $element.data('nonce'),
724 cookie: encodeURIComponent(document.cookie),
725 }, function(json_export_data) {
726 var overlay = overlay_show('export');
727 overlay.find('textarea').val( $('<div/>').text(json_export_data).html() );
728 }).fail( function( jqXHR, textStatus, errorThrown ) {
729 overlay_show('message', 'An unexpected error has occurred while exporting player #' + player_id + ': <code>' + errorThrown + '</code><br /><br />Please try again.');
730 });
731
732 },
733 onClosed : overlay_hide,
734 onOpen: lightbox_open
735 } );
736
737 return false;
738 });
739
740 $doc.on( 'click', '.fv-player-import', function(e) {
741 e.preventDefault();
742 $.fv_player_box( {
743 onComplete : function() {
744 overlay_show('import');
745 },
746 onClosed : overlay_hide,
747 onOpen: lightbox_open
748 } );
749
750 return false;
751 });
752
753 $doc.on( 'click', '.fv-player-remove', function() {
754 jQuery(this)
755 .addClass('fv-player-remove-confirm')
756 .removeClass('fv-player-remove')
757 .html('Are you sure?')
758 .one('mouseleave', function() {
759 jQuery(this)
760 .removeClass('fv-player-remove-confirm')
761 .addClass('fv-player-remove')
762 .html('Delete');
763 });
764
765 return false;
766 });
767
768 $doc.on( 'click', '.fv-player-remove-confirm', function() {
769 let
770 $element = $(this),
771 $row_actions = $element.closest( '.row-actions' ),
772 $element_td = $element.parent(),
773 $spinner = $('<div class="fv-player-shortcode-editor-small-spinner"></div>'),
774 player_id = $element.data('player_id');
775
776 $element_td.find('a, span').hide();
777 $element.after($spinner);
778
779 // Make sure the row actions do not show on hover only, but always appear to make sure the spinner remains visible
780 $row_actions.css( 'left', 0 );
781
782 debug_log('Running fv_player_db_remove Ajax for #' + player_id );
783
784 jQuery.post(ajaxurl, {
785 action: "fv_player_db_remove",
786 nonce: $element.data('nonce'),
787 playerID: player_id
788 }, function(rows_affected){
789 if (!isNaN(parseFloat(rows_affected)) && isFinite(rows_affected)) {
790 // remove the deleted player's row
791 $element.closest('tr').hide('slow', function() {
792 jQuery(this).remove();
793 });
794 } else {
795 $spinner.remove();
796
797 alert(rows_affected);
798
799 $element_td.find('span, a:not(.fv-player-remove-confirm)').show();
800 }
801 }).fail(function() {
802 $spinner.remove();
803
804 $element.html('Error');
805
806 $element_td.find('span, a:not(.fv-player-remove-confirm)').show();
807 });
808
809 return false;
810 });
811
812 $doc.on( 'click', '.fv-player-clone', function() {
813 var $element = jQuery(this),
814 $spinner = $('<div class="fv-player-shortcode-editor-small-spinner">&nbsp;</div>');
815
816 $element
817 .hide()
818 .after($spinner);
819
820 debug_log('Running fv_player_db_clone Ajax.');
821
822 $.post(ajaxurl, {
823 action: "fv_player_db_clone",
824 nonce: $element.data('nonce'),
825 playerID: $element.data('player_id')
826 }, function(playerID){
827 if (playerID != '0' && !isNaN(parseFloat(playerID)) && isFinite(playerID)) {
828 // add the inserted player's row
829 $.get(
830 fv_player_editor_conf.admin_url + '&id=' + playerID,
831 function (response) {
832 $('#the-list tr:first').before(jQuery(response).find('#the-list tr:first'));
833 $spinner.remove();
834 $element.show();
835 }).fail(function() {
836 $spinner.remove();
837 $element.show();
838 });
839 } else {
840 $spinner.remove();
841 $element.show();
842 alert(playerID); // show respone message
843 }
844 }).fail(function() {
845 $spinner.remove();
846 $element.show();
847 alert('Error');
848 });
849
850 return false;
851 });
852
853 }
854
855 // Gutenberg style checkboxes
856 $el_editor.on( 'change', '.components-form-toggle input[type=checkbox]', function() {
857 var wrap = $(this).closest('.components-form-toggle'),
858 checked = $(this).prop('checked'),
859 name = $(this).attr('name').replace( /fv_wp_flowplayer_field_/, '' );
860
861 checkbox_toggle_worker(wrap, name, checked);
862 });
863
864 $el_editor.on('change input', '.components-text-control__input, .components-select-control__input', function() {
865 var input = jQuery(this),
866 parent = input.closest('.fv-player-editor-children-wrap'),
867 name = input.attr('name').replace( /fv_wp_flowplayer_field_/, '' ),
868 wrap = input.parents( '.fv-player-editor-field-wrap-' + name );
869
870 text_and_select_worker( input, parent, name, wrap );
871 });
872
873 /*
874 * Intro view text input and button
875 */
876
877 // Copy hero input value to the first video src and trigger event for save & preview
878 $('[name=hero-src]').on('change keyup', function() {
879
880 $('#playlist-hero .fv-player-editor-notice.notice-use-ui').hide();
881
882 let input = $( this ),
883 value = input.val().trim(),
884 is_valid_url = false;
885
886 if( value ) {
887 if( value.match( /https?:\/\// ) ) {
888
889 for (var vtype in window.fv_player_editor_matcher) {
890 if (window.fv_player_editor_matcher[vtype].matcher.exec(value) !== null) {
891 is_valid_url = true;
892 break;
893 }
894 }
895
896 }
897
898 if( $( '#playlist-hero:visible' ).length ) {
899 $('#playlist-hero .fv-player-editor-notice.notice-url-format').toggle( !is_valid_url );
900 } else {
901 $('#fv-player-shortcode-editor-preview-no .fv-player-editor-notice.notice-url-format').toggle( !is_valid_url );
902 }
903 }
904
905 if( !is_valid_url ) {
906 return false;
907 }
908
909 get_hero_src_field( $(this) ).val( value ).trigger( 'keyup' );
910 });
911
912 $('.button-hero').on('click', function() {
913 // click on the media library button next to that input
914 get_hero_src_field( $(this) ).closest('.components-base-control__field').find('.add_media').trigger('click');
915 });
916
917 /*
918 * NAV TABS
919 */
920 $('.fv-player-tabs-header a').on( 'click', function(e) {
921 e.preventDefault();
922 $('.fv-player-tabs-header a').removeClass('nav-tab-active');
923 $(this).addClass('nav-tab-active')
924 $('.fv-player-tabs > .fv-player-tab').hide();
925 $('.' + $(this).data('tab')).show();
926
927 current_editor_tab = $(this).data('tab');
928 });
929
930 /*
931 * Select playlist item
932 * keywords: select item
933 */
934 $doc.on('click','.fv-player-editor-playlist-item .configure-video, .fv-player-editor-playlist-item .fvp_item_video-thumbnail', function() {
935 if( fv_player_editor_conf.hide && fv_player_editor_conf.hide.indexOf( 'configure-video' ) > 0 ) {
936 return false;
937 }
938
939 playlist_item_show( $(this).parents('.fv-player-editor-playlist-item').attr('data-index') );
940 return false;
941 });
942
943 /*
944 * Show edit input
945 * keywords: edit playlist items edit playlist items
946 */
947 $doc.on('click','.fv-player-tab-playlist .fv-player-editor-playlist-item .fvp_item_video-filename', function(e) {
948 e.stopPropagation();
949
950 var
951 $parent = $(e.target).closest('[data-index]'),
952 filename = $parent.find('.fvp_item_video-filename'),
953 wrap = $parent.find('.fvp_item_video-title-wrap'),
954 input = $parent.find('.fvp_item_video-edit-input');
955
956 wrap.hide();
957 input.val(filename.text()).show().focus();
958
959 is_editing_playlist_item_title = true;
960 });
961
962 // Clicking anywhere it the editor should close the playlist item title editing
963 $el_editor.on( 'click', function(e) {
964 if( is_editing_playlist_item_title ) {
965 // Except the title input box
966 if( jQuery(e.target).hasClass('fvp_item_video-edit-input') ) {
967 return;
968 }
969
970 is_editing_playlist_item_title = false;
971
972 title_editor_close();
973 }
974 });
975
976 /*
977 * Edit title
978 * keywords: edit title playlist items edit title playlist items
979 */
980 $doc.on('keyup','.fv-player-tab-playlist .fv-player-editor-playlist-item .fvp_item_video-edit-input', function(e) {
981 e.stopPropagation();
982
983 if( e.key == "Enter" ) {
984 title_editor_close();
985 return;
986 }
987
988 var
989 $parent = $(e.target).parents('[data-index]'),
990 index = $parent.attr('data-index'),
991 video_tab = get_tab(index, 'video-files'),
992 new_title = jQuery(this).val();
993
994 $parent.find('.fvp_item_video-filename').text( new_title );
995 get_field('title', video_tab).val( new_title ).trigger('keyup');
996 });
997
998 /*
999 * Remove playlist item
1000 * keywords: delete playlist items remove playlist items
1001 */
1002 $doc.on('click','.fv-player-tab-playlist .fv-player-editor-playlist-item .fvp_item_remove', function(e) {
1003 e.stopPropagation();
1004
1005 if( !confirm('Would you like to remove this video?') ) {
1006 return false;
1007 }
1008
1009 var
1010 playlist_row = $(e.target).parents('[data-index]'),
1011 index = playlist_row.attr('data-index'),
1012 id = get_tab(index,'video-files').attr('data-id_video');
1013
1014 deleted_videos.push(id);
1015
1016 playlist_row.remove();
1017
1018 get_tab(index,'video-files').remove();
1019 get_tab(index,'subtitles').remove();
1020 get_tab(index,'cues').remove();
1021
1022 // Keep data-index in order
1023 playlist_index();
1024
1025 // if no playlist item is left, add a new one
1026 // TODO: Some better way? Like do it after the data is saved
1027 if( !jQuery('.fv-player-tab-subtitles [data-playlist-item][data-index]').length ){
1028
1029 // Required, with this it actually saves the change!
1030 playlist_item_add();
1031
1032 if( fv_player_editor_conf.frontend ) {
1033 reset_preview();
1034 playlist_show();
1035
1036 } else {
1037 playlist_item_show(0);
1038 }
1039 }
1040
1041 $doc.trigger('fv_player_editor_item_delete');
1042
1043 return false;
1044 });
1045
1046 /*
1047 * Sort playlist
1048 */
1049 $('.fv-player-tab-playlist #fv-player-editor-playlist').sortable({
1050 start: function() {
1051 store_rtmp_server = get_field( 'rtmp', get_tab('first','video-files') ).val();
1052 },
1053 update: playlist_sortable_update,
1054 axis: 'y',
1055 handle: '.fv-player-editor-playlist-move-handle',
1056 containment: ".fv-player-tab-playlist"
1057 });
1058
1059 $doc.on('click','.fv-player-editor-playlist-item .fv-player-editor-playlist-move-up, .fv-player-editor-playlist-item .fv-player-editor-playlist-move-down', function(e) {
1060 var button = $(e.target),
1061 item = button.closest('[data-index]');
1062
1063 if( button.hasClass('fv-player-editor-playlist-move-up') ) {
1064 item.fadeOut( 250, function() {
1065 item.insertBefore( item.prev() );
1066 item.fadeIn( 250, playlist_sortable_update );
1067 });
1068
1069 } else {
1070 item.fadeOut( 250, function() {
1071 item.insertAfter( item.next() );
1072 item.fadeIn( 250, playlist_sortable_update );
1073 });
1074 }
1075
1076 });
1077
1078 function playlist_sortable_update() {
1079 $doc.trigger('fv-player-editor-sortable-update');
1080 var new_sort = [];
1081 $('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').each(function(){
1082 var
1083 index = $(this).attr('data-index'),
1084 video_tab_item = get_tab(index,'video-files'),
1085 subtitle_tab_item = get_tab(index,'subtitles');
1086
1087 new_sort.push({
1088 video_tab_item : video_tab_item.clone(),
1089 subtitle_tab_item : subtitle_tab_item.clone()
1090 });
1091
1092 video_tab_item.remove();
1093 subtitle_tab_item.remove();
1094 });
1095
1096 $.each(new_sort, function(k,v) {
1097 $('.fv-player-tab-video-files').append(v.video_tab_item);
1098 $('.fv-player-tab-subtitles').append(v.subtitle_tab_item);
1099 });
1100
1101 get_field( 'rtmp', get_tab('first','video-files') ).val( store_rtmp_server );
1102
1103 playlist_index();
1104
1105 $doc.trigger('fv_player_editor_playlist_sort');
1106 }
1107
1108 /*
1109 * Uploader
1110 */
1111 var fv_flowplayer_uploader;
1112 var fv_flowplayer_uploader_button;
1113
1114 $doc.on( 'click', '#fv-player-shortcode-editor .components-button.add_media, .fv-player-gutenberg-media', fv_flowplayer_uploader_init );
1115
1116 /**
1117 * In order for Media Library to work we need:
1118 * - set fv_flowplayer_uploader_button
1119 * - add .fv_flowplayer_target to the input field that will be updated
1120 */
1121 function fv_flowplayer_uploader_init(e) {
1122 e.preventDefault();
1123
1124 fv_flowplayer_uploader_button = jQuery(this);
1125
1126 var el_input = fv_flowplayer_uploader_button.closest('.components-base-control__field').find('[name=' + fv_flowplayer_uploader_button.data('target') + ']');
1127
1128 // Fallback to previous input, used in FV Player Block for the "Settings" sidebar, also called "Inspector"
1129 if ( ! el_input.length ) {
1130 el_input = fv_flowplayer_uploader_button.prev('.components-panel__row').find('input');
1131 }
1132
1133 /**
1134 * Mark the input as target.
1135 *
1136 * If using Gutenberg block .fv_flowplayer_target won't be added anywhere, but it does not matter.
1137 * We update the block properites directly.
1138 */
1139 if( el_input.length ) {
1140 // Remove target from the previous field
1141 jQuery('.fv_flowplayer_target').removeClass('fv_flowplayer_target' );
1142
1143 el_input.addClass('fv_flowplayer_target');
1144 }
1145
1146 fv_flowplayer_uploader_open();
1147 }
1148
1149 function fv_flowplayer_uploader_open() {
1150 debug_log( 'Opening Media Library...' );
1151
1152 //If the uploader object has already been created, reopen the dialog
1153 if (fv_flowplayer_uploader) {
1154 fv_flowplayer_uploader.open();
1155 return;
1156 }
1157
1158 //Extend the wp.media object
1159 fv_flowplayer_uploader = wp.media.frames.file_frame = wp.media({
1160 title: 'Add Video',
1161 button: {
1162 text: 'Choose'
1163 },
1164 multiple: false
1165 });
1166
1167 fv_flowplayer_uploader.on('open', function() {
1168 $( document ).trigger( "mediaBrowserOpen" );
1169 jQuery('.media-router .media-menu-item').eq(0).trigger('click');
1170 jQuery('.media-frame-title h1').text(fv_flowplayer_uploader_button.text());
1171
1172 // Hide Media Library tabs which are not allowed
1173 if( fv_player_editor_conf.library ) {
1174 let libraries = fv_player_editor_conf.library.split(/,/),
1175 library_found = false;
1176
1177 $( '.media-router .media-menu-item' ).each( function( i, el ) {
1178 let found = false;
1179 $( libraries ).each( function( v, library ) {
1180 if( $(el).attr('id').match(library) ) {
1181 found = library_found = true;
1182 $(el).trigger( 'click' );
1183 }
1184 });
1185
1186 if( !found ) {
1187 $(el).hide();
1188 }
1189 });
1190
1191 // At least close the Media Library if the desired that was not found
1192 // TODO: It should not open at all if the required library is not found
1193 if ( ! library_found ) {
1194 debug_log('Libraries '+fv_player_editor_conf.library+' not found!');
1195
1196 fv_flowplayer_uploader.close();
1197 }
1198 }
1199 });
1200
1201 //When a file is selected, grab the URL and set it as the text field's value
1202 fv_flowplayer_uploader.on('select', function() {
1203 var attachment = fv_flowplayer_uploader.state().get('selection').first().toJSON();
1204 var target_element = $('.fv_flowplayer_target');
1205
1206 // Update the HTML input field
1207 if ( target_element.length ) {
1208 target_element.val(attachment.url).trigger('change').trigger('keyup');
1209 target_element.removeClass('fv_flowplayer_target' );
1210
1211 }
1212
1213 // Did we open the media library from within the block?
1214 if ( fv_flowplayer_uploader_button.hasClass( 'fv-player-gutenberg-media' ) ) {
1215 var clientId = null;
1216
1217 if ( typeof wp !== 'undefined' && wp.data && wp.data.select( 'core/block-editor' ) ) {
1218 clientId = wp.data.select( 'core/block-editor' ).getSelectedBlockClientId();
1219 }
1220
1221 if ( ! clientId ) {
1222 clientId = jQuery('.is-selected[data-type="fv-player-gutenberg/basic"]').data('block');
1223 }
1224
1225 if ( ! clientId ) {
1226 clientId = jQuery('iframe[name="editor-canvas"], iframe.editor-canvas__iframe, iframe.edit-site-visual-editor__editor-canvas').contents().find('.is-selected[data-type="fv-player-gutenberg/basic"]').data('block');
1227 }
1228
1229 if ( clientId ) {
1230 debug_log( 'Media library updating block attributes.' );
1231
1232 wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes(clientId, { src: attachment.url });
1233 }
1234 }
1235
1236 if( attachment.type == 'video' ) {
1237 // TODO: Fill video title
1238
1239 } else if( attachment.type == 'image' ) {
1240 if( attachment.id ) {
1241 // update splash attachent id
1242 get_field( 'splash_attachment_id', true ).val(attachment.id);
1243 }
1244
1245 if( typeof(fv_flowplayer_set_post_thumbnail_id) != "undefined" ) {
1246 if( jQuery('#remove-post-thumbnail').length > 0 ) {
1247 return;
1248 }
1249
1250 debug_log('Running set-post-thumbnail Ajax.');
1251
1252 jQuery.post(ajaxurl, {
1253 action:"set-post-thumbnail",
1254 post_id: fv_flowplayer_set_post_thumbnail_id,
1255 thumbnail_id: attachment.id,
1256 _ajax_nonce: fv_flowplayer_set_post_thumbnail_nonce,
1257 cookie: encodeURIComponent(document.cookie)
1258 }, function(str){
1259 if ( str == '0' ) {
1260 alert( setPostThumbnailL10n.error );
1261 } else {
1262 jQuery('#postimagediv .inside').html(str);
1263 jQuery('#postimagediv .inside #plupload-upload-ui').hide();
1264 }
1265 } );
1266 }
1267 }
1268 });
1269
1270 //Open the uploader dialog
1271 fv_flowplayer_uploader.open();
1272 }
1273
1274 template_playlist_item = jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').parent().html();
1275 template_video = get_tab('first','video-files').parent().html();
1276 template_subtitles_tab = jQuery('.fv-player-tab-subtitles').html();
1277
1278 /*
1279 * End of playlist Actions
1280 */
1281
1282 jQuery('#fv_wp_flowplayer_field_end_actions').on( 'change', show_end_actions );
1283
1284
1285 /*
1286 * Preview iframe dialog resize
1287 */
1288 $doc.on('fvp-preview-complete',function() {
1289 $el_preview.attr('class','preview-show');
1290
1291 // If editor was in the intro mode, we show the playlist and enable the full-editor
1292 if( $el_editor.hasClass('is-intro') ) {
1293 playlist_show();
1294
1295 $el_editor.removeClass('is-intro');
1296 $('[name=hero-src]').val('');
1297 }
1298
1299 // If editor was in the frontend mode and adding playlist item we hide the playlist hero item now
1300 // and instead show the playlist item row
1301 if( $( '#playlist-hero:visible' ).length ) {
1302 playlist_hero_hide();
1303 }
1304 });
1305
1306 /*
1307 * Video share option
1308 */
1309
1310 // TODO: Check
1311 jQuery('#fv_wp_flowplayer_field_share').on( 'change', function(){
1312 var value = jQuery(this).val();
1313
1314 switch(value){
1315 case 'Custom':
1316 jQuery("#fv_wp_flowplayer_field_share_custom").show();
1317 break;
1318 default:
1319 jQuery("#fv_wp_flowplayer_field_share_custom").hide();
1320 break;
1321 }
1322 });
1323
1324 $doc.on("change", "#fv-player-shortcode-editor-right input, #fv-player-shortcode-editor-right select", save );
1325
1326 $doc.on("keyup", "#fv-player-shortcode-editor-right input[type=number], #fv-player-shortcode-editor-right input[type=text], #fv-player-shortcode-editor-right textarea", function(e) {
1327 clearTimeout(int_keyup);
1328 int_keyup = setTimeout( function() {
1329 save(e);
1330 }, fv_player_editor_conf.keyup_throttle );
1331 });
1332
1333 $doc.on('fv_flowplayer_shortcode_new fv-player-editor-non-db-shortcode', function() {
1334 insert_button_toggle(true);
1335 copy_player_button_toggle(true);
1336
1337 ajax_saving = false;
1338 });
1339
1340 $doc.on('fv_player_editor_player_loaded', function() {
1341 ajax_saving = false;
1342 is_unsaved = false;
1343 });
1344
1345 $doc.on('fv_flowplayer_player_editor_reset', function() {
1346 ajax_saving = true;
1347 is_unsaved = true;
1348 has_draft_status = true;
1349 //is_draft_changed = false;
1350 });
1351
1352 $doc.on('fv_player_editor_playlist_sort', save );
1353 $doc.on('fv_player_editor_item_delete', save );
1354 $doc.on('fv_player_editor_language_delete', save );
1355
1356 /*
1357 * @param {object} [e] The event handle is invoked by input change event
1358 */
1359 function save(e) {
1360 // "ajax_saving" is implicitly set to true to make sure we wait with any saving until
1361 // all existing player's data are loaded and filled into inputs
1362 if ( ajax_saving ) {
1363 return;
1364 }
1365
1366 // Skips the interface toggles (Advanced Settings, RTMP)
1367 if( e && $(e.target).hasClass('no-data') ) {
1368 return;
1369 }
1370
1371 if( e && $(e.target).data('no-reload') ) {
1372 prevent_reload_for_current_save = true;
1373 } else {
1374 prevent_reload_for_current_save = false;
1375 }
1376
1377 var
1378 ajax_data = build_ajax_data(true),
1379 db_data_loading = true;
1380
1381 for ( var item in ajax_data.videos ) {
1382 // we have video data already loaded from DB,
1383 // ... this is the only way to see if we actually have
1384 // at least a single object key while preserving
1385 // browsers compatibility for browsers without support
1386 // for Object.keys()
1387 db_data_loading = false;
1388 break;
1389 }
1390
1391 if ( db_data_loading || !ajax_data.videos ) {
1392 // editing a player and still waiting for the videos to load from DB
1393 return;
1394 }
1395
1396 if( ajax_save_previous && JSON.stringify(ajax_data) == JSON.stringify(ajax_save_previous) ) {
1397 debug_log('No changes to save.');
1398 return;
1399 }
1400
1401 if( is_saving ) {
1402 debug_log('Still saving!');
1403 next = true;
1404 return;
1405 }
1406
1407 ajax_save_previous = ajax_data;
1408
1409 ajax(ajax_data);
1410
1411 }
1412
1413 function ajax( data ) {
1414 ajax_save_this_please = data;
1415 }
1416
1417 function error(msg) {
1418 is_saving = false;
1419 $el_editor.find('.button-primary').prop('disabled', false);
1420
1421 overlay_show('message', 'An unexpected error has occurred. Please try again. '+msg, true );
1422 }
1423
1424 setInterval( function() {
1425 if ( !ajax_save_this_please || is_loading_meta_data ) return;
1426
1427 // show error overlay if we have errors
1428 var err = has_errors();
1429 if ( err ) {
1430 add_notice( 'error', err );
1431 return;
1432 }
1433
1434 var what_is_saving = ajax_save_this_please;
1435
1436 is_saving = true;
1437 insert_button_toggle_disabled(true);
1438
1439 el_spinner.show();
1440
1441 remove_notices();
1442
1443 ajax_save_this_please = set_control_fields( ajax_save_this_please );
1444
1445 debug_log('Running fv_player_db_save Ajax.', ajax_save_this_please.update );
1446
1447 let time_start_save = performance.now();
1448
1449 $.ajax({
1450 type: 'POST',
1451 url: ajaxurl + '?action=fv_player_db_save',
1452 data: JSON.stringify( {
1453 data: ajax_save_this_please,
1454 nonce: fv_player_editor_conf.edit_nonce,
1455 } ),
1456 contentType: 'application/json',
1457 dataType: 'json',
1458 success: function(response) {
1459
1460 debug_log( 'Finished fv_player_db_save Ajax in ' + debug_time( time_start_save ) + '.' );
1461
1462 if( response.error ) {
1463 if( response.fatal_error ) {
1464
1465 debug_log( 'Fatal error in fv_player_db_save: ' + response.fatal_error );
1466
1467 var json_export_data = jQuery('<div/>').text(JSON.stringify(what_is_saving)).html();
1468
1469 var overlay = overlay_show('error_saving');
1470 overlay.find('textarea').val( response.error + '\n\nJSON data:\n\n' + $('<div/>').text(json_export_data).html() );
1471
1472 jQuery('#fv_player_copy_to_clipboard').select();
1473
1474 } else {
1475 add_notice( 'error', response.error );
1476
1477 debug_log( 'Error in fv_player_db_save: ' + response.error );
1478 }
1479
1480 el_spinner.hide();
1481
1482 is_saving = false;
1483
1484 return;
1485 }
1486
1487 let time_start_display = performance.now();
1488
1489 debug_log('player ID after save: '+response.id);
1490
1491 current_player_object = response;
1492 current_player_db_id = response.id;
1493
1494 try {
1495 $(response.videos).each( function(k,v) {
1496 var item = $('.fv-player-playlist-item').eq(k);
1497 if( !item.data('id_video') ) {
1498 item.attr('data-id_video',v.id);
1499 }
1500
1501 if( k == current_video_to_edit ) {
1502 debug_log('current_video_db_id after save: '+v.id);
1503 current_video_db_id = v.id;
1504 }
1505
1506 /*
1507 * The video saving might fetch the video duration, splash screen and title...
1508 * So we fill that in
1509 */
1510 // TODO: Populate chapters, error, is_live, is_audio, encryption key
1511 var video_tab = get_tab( k, 'video-files' ),
1512 subtitles_tab = get_tab( k, 'subtitles' ),
1513 splash_field = get_field( 'splash', video_tab ),
1514 splash_attachment_id_field = get_field( 'splash_attachment_id', video_tab ),
1515 title_field = get_field( 'title', video_tab ),
1516 auto_splash = get_playlist_video_meta_value( 'auto_splash', k ),
1517 auto_caption = get_playlist_video_meta_value( 'auto_caption', k );
1518
1519 if( get_field('auto_splash', video_tab ).val() == '0' ) {
1520 auto_splash = false;
1521 }
1522
1523 if( get_field('auto_caption', video_tab ).val() == '0' ) {
1524 auto_caption = false;
1525 }
1526
1527 if( v.splash && ( !splash_field.val() || auto_splash ) ) {
1528 splash_field.val( v.splash );
1529
1530 if( v.splash_attachment_id && !splash_attachment_id_field.val() ) {
1531 splash_attachment_id_field.val( v.splash_attachment_id )
1532 }
1533 }
1534
1535 // Populate video meta fields for which the video checking on video save has added value
1536 Object.keys( window.fv_player_editor_fields ).forEach( function( field_name ) {
1537 if ( 'video_meta' == window.fv_player_editor_fields[ field_name].store ) {
1538 let field_value = get_playlist_video_meta_value( field_name, k );
1539 if( field_value ) {
1540 if ( !get_field( field_name, video_tab ).val() ) {
1541 get_field( field_name, video_tab ).val( field_value );
1542 }
1543
1544 if ( !get_field( field_name, subtitles_tab ).val() ) {
1545 get_field( field_name, subtitles_tab ).val( field_value );
1546 }
1547 }
1548 }
1549 } );
1550
1551 if( auto_splash ) {
1552 get_field('auto_splash', video_tab ).val( auto_splash );
1553 }
1554
1555 if( v.title && ( !title_field.val() || auto_caption ) ) {
1556 title_field.val( v.title );
1557
1558 text_and_select_worker( title_field, title_field.closest('.fv-player-editor-children-wrap'), 'title', title_field.parents( '.fv-player-editor-field-wrap-title' ) );
1559 }
1560
1561 if( auto_caption ) {
1562 get_field('auto_caption', video_tab ).val( auto_caption );
1563 }
1564
1565 // The Ajax save can give us some video details and it detects stream type so we refresh that information here if we are editing that video
1566 if( current_video_to_edit == k ) {
1567 show_video_details(k);
1568 show_stream_fields_worker(k);
1569 show_playlist_not_supported(k);
1570 }
1571 });
1572
1573 // If we are in playlist view, we refresh the list too
1574 if( current_video_to_edit == -1 ) {
1575 playlist_refresh();
1576 }
1577
1578 /**
1579 * Allow plugins to fill in the hidden fields with data coming back from save response.
1580 * These are the fields which hold ID of any additional items created, like PPV product IDs.
1581 * Their IDs need to be taken into consideration right away as otherwise the PPV products might multiple
1582 * if you make more changes to the price and save again before the initial save is done.
1583 */
1584 $doc.trigger( 'fv_flowplayer_player_meta_load_high_priority', [ response ] );
1585
1586 debug_log( 'Finished populating editor fields for ' + response.videos.length + ' videos in ' + debug_time( time_start_display ) + '.' );
1587
1588 // Did the data change while saving?
1589 if( next ) {
1590 debug_log('There is more to save...');
1591
1592 if ( is_unsaved ) {
1593 init_saved_player_fields();
1594 }
1595
1596 ajax( build_ajax_data(true) );
1597 next = false;
1598 } else {
1599 is_saving = false;
1600
1601 insert_button_toggle_disabled(false);
1602
1603 el_spinner.hide();
1604
1605 add_notice( 'success', 'Saved!', 2500 );
1606
1607 // close the overlay, if we're waiting for the save
1608 if (overlay_close_waiting_for_save) {
1609 // add this player's ID into players that no longer need an edit lock
1610 if (current_player_db_id > 0) {
1611 edit_lock_removal[current_player_db_id] = 1;
1612 }
1613 overlay_close_waiting_for_save = false;
1614 $.fn.fv_player_box.close();
1615
1616 } else if ( typeof( response.html ) != "undefined" && !prevent_reload_for_current_save ) {
1617 if( response.html ) {
1618 // auto-refresh preview
1619 el_preview_target.html( response.html );
1620
1621 $doc.trigger('fvp-preview-complete');
1622
1623 } else {
1624 reset_preview();
1625 }
1626 }
1627
1628 prevent_reload_for_current_save = false;
1629
1630 // if we're creating a new player, hide the Save / Insert button and
1631 // add all the data and inputs to page that we need for an existing player
1632 if ( is_unsaved ) {
1633 copy_player_button_toggle(false);
1634 init_saved_player_fields();
1635 is_unsaved = false;
1636 //is_draft_changed = false;
1637 ajax_saving = false;
1638 ajax_save_this_please = false;
1639 }
1640
1641 // Output the shortcode into the pre-configured output field
1642 if( fv_player_editor_conf.field_selector ){
1643 insert_shortcode( '[fvplayer id="'+current_player_db_id+'"]');
1644 }
1645
1646 // Allow plugins to fill in the field with data coming back from save response
1647 $doc.trigger( 'fv_flowplayer_player_meta_load', [ response ] );
1648
1649 // Set the current data as previous to let auto-saving detect changes
1650 // For new player this will have video and player IDs
1651 ajax_save_previous = build_ajax_data(true);
1652 }
1653 } catch(e) {
1654 error(e);
1655 }
1656
1657 },
1658 error: function( jqXHR, textStatus, errorThrown) {
1659 add_notice( 'error', '<p>Error saving changes: ' + errorThrown + ': ' + jqXHR.responseText + '</p>' );
1660
1661 debug_log( 'HTTP Error in fv_player_db_save: ' + errorThrown + ': ' + jqXHR.responseText );
1662
1663 el_spinner.hide();
1664
1665 is_saving = false;
1666 }
1667 });
1668
1669 ajax_save_this_please = false;
1670
1671 }, 1500 );
1672
1673 editor_init();
1674
1675 var $body = jQuery('body');
1676 $body.on('focus', '#fv_player_copy_to_clipboard', function() {
1677 this.select();
1678 });
1679
1680 jQuery('.fv-player-editor-wrapper').each( function() { fv_show_video( jQuery(this) ) }); // show last add more button only
1681
1682 $doc.on( 'fv_flowplayer_shortcode_insert', '.fv-player-editor-field', function() {
1683 fv_load_video_preview( jQuery(this).parents('.fv-player-editor-wrapper'));
1684 } );
1685
1686 /*
1687 Custom Videos feature
1688 TODO: Test
1689 */
1690 function fv_show_video( wrapper ) {
1691 if( wrapper.find('.fv-player-editor-field').val() ) {
1692 wrapper.find('.edit-video').show();
1693 wrapper.find('.add-video').hide();
1694 }
1695 else {
1696 wrapper.find('.edit-video').hide();
1697 wrapper.find('.add-video').show();
1698 wrapper.find('.fv-player-editor-preview').html('');
1699 }
1700
1701 jQuery('[data-key='+wrapper.data('key')+'] .fv-player-editor-more').last().show(); // show last add more button only
1702 }
1703
1704 /*
1705 Custom Videos feature
1706 TODO: Test
1707 */
1708 function fv_remove_video( id ) {
1709 $( '#widget-widget_fvplayer-'+id+'-text' ).val("");
1710 fv_show_video(id);
1711 $('#fv_edit_video-'+id+' .video-preview').html('');
1712 }
1713
1714 /*
1715 Custom Videos feature
1716 TODO: Test
1717 */
1718 function fv_load_video_preview( wrapper ) {
1719 var shortcode = $(wrapper).find('.fv-player-editor-field').val();
1720 var indicator = $("<div class='fv-player-editor-player-loading'><span class='waiting spinner is-active'></span></div>").appendTo('.fp-playlist-external');
1721
1722 if( shortcode && shortcode.length === 0 ) {
1723 return false;
1724 }
1725
1726 shortcode = shortcode.replace( /(width=[\'"])\d*([\'"])/, "$1320$2" ); // 320
1727 shortcode = shortcode.replace( /(height=[\'"])\d*([\'"])/, "$1240$2" ); // 240
1728
1729 var url = fv_player_editor_conf.home_url + '?fv_player_preview_nonce='+fv_player_editor_conf.preview_nonce+'&fv_player_preview=' + fv_player_editor.b64EncodeUnicode(shortcode);
1730 $.get(url, function(response) {
1731 wrapper.find('.fv-player-editor-preview').html( jQuery('#wrapper',response ) );
1732 $doc.trigger('fvp-preview-complete', [ shortcode, wrapper.data('key'), wrapper ] );
1733 indicator.remove();
1734 } );
1735
1736 fv_show_video(wrapper);
1737 }
1738
1739 $doc.on('click','.fv-player-editor-remove', function() {console.log('.fv-player-editor-remove');
1740 var wrapper = $(this).parents('.fv-player-editor-wrapper');
1741 if( $('[data-key='+wrapper.data('key')+']').length == 1 ) { // if there is only single video
1742 wrapper.find('.fv-player-editor-field').val('');
1743 fv_show_video(wrapper);
1744 } else {
1745 wrapper.remove();
1746 jQuery('.fv-player-editor-wrapper').each( function() { fv_show_video( jQuery(this) ) }); // show last add more button only
1747 }
1748 return false;
1749 });
1750
1751 $doc.on('click','.fv-player-editor-more', function() {
1752 var wrapper = $(this).parents('.fv-player-editor-wrapper');
1753 var new_wrapper = wrapper.clone();
1754 new_wrapper.find('.fv-player-editor-field').val('');
1755 fv_show_video(new_wrapper);
1756 new_wrapper.insertAfter( $('[data-key='+wrapper.data('key')+']:last') ); // insert after last of the kind
1757 $(this).hide();
1758
1759 return false;
1760 });
1761
1762 $doc.on( 'click', '.fv-player-shortcode-copy', function() {
1763 var button = $(this);
1764 fv_player_clipboard( $(this).parents('tr').find('.fv-player-shortcode-input').val(), function() {
1765 button.html('Coppied to clipboard!');
1766 setTimeout( function() {
1767 button.html('Copy Shortcode');
1768 }, 1000 );
1769 }, function() {
1770 button.html('Error');
1771 } );
1772 return false;
1773 });
1774
1775 $doc.on('click', '#fv-player-editor-copy_player-overlay .attachment', function() {
1776 let item = $(this),
1777 sidebar = $('#fv-player-editor-copy_player-overlay .media-sidebar'),
1778 attachment_details = sidebar.find( '.attachment-details' ),
1779 details = item.data('details');
1780
1781 $( '#fv-player-editor-copy_player-overlay .attachment' ).removeClass( 'selected details' );
1782
1783 item.addClass('selected details');
1784
1785 attachment_details.find('.filename').text( '#' + details.id + '. '+ details.player_name );
1786 attachment_details.find('.uploaded').text( details.date_created );
1787
1788 attachment_details.find('.videos-list').html( '<ul><li>' + details.video_titles.join('</li><li>' ) + '</li></ul>' );
1789
1790 let ul = $('<ul></ul>');
1791 $.each( details.embeds, function( k, v ) {
1792 let type = v.post_type != 'post' ? ' (' + v.post_type + ')' : '',
1793 status = v.post_status != 'publish' ? ' (' + v.post_status + ')' : '',
1794 taxonomies_and_terms = [];
1795
1796 if( v.taxonomies ) {
1797 $.each( v.taxonomies, function( taxonomy, taxonomy_details ) {
1798
1799 let taxonomy_terms = [];
1800 $.each( taxonomy_details.terms, function( k, term ) {
1801 taxonomy_terms.push( term.name );
1802 } );
1803 taxonomies_and_terms.push( taxonomy_details.label + ': ' + taxonomy_terms.join( ', ' ) );
1804 } );
1805 }
1806
1807 taxonomies_and_terms = '<ul><li>' + taxonomies_and_terms.join( '</li><li>' ) + '</li></ul>';
1808
1809 ul.append( '<li><strong>' + v.post_title + '</strong>' + type + status + taxonomies_and_terms + '</li>')
1810 } );
1811 attachment_details.find('.posts-list').html('').append( ul );
1812
1813 attachment_details.show();
1814
1815 $( '#fv-player-editor-copy_player-overlay .button-primary' ).prop( 'disabled', false );
1816
1817 });
1818
1819 $doc.on('click', '#fv-player-editor-copy_player-overlay .button-primary', function() {
1820 $el_editor.find('.button-primary').text('Insert');
1821
1822 let selected = $( '#fv-player-editor-copy_player-overlay .attachment.selected' );
1823 if( selected.length > 0 ) {
1824 editor_open( selected.data('details').id );
1825 }
1826
1827 });
1828
1829 $doc.on('click', '.fv_player_field_insert-button', function() {
1830 if (is_saving || ajax_save_this_please) {
1831 // for some reason, clicking on already-disabled primary button re-enables it,
1832 // so we'll just need to disable it again here
1833 insert_button_toggle_disabled(true);
1834 } else {
1835 // make sure we mark this player as published in the DB
1836 has_draft_status = false;
1837 editor_submit();
1838 }
1839
1840 });
1841
1842 $doc.on('click', '.playlist_add', function() {
1843 // If it's front-end mode do not add new item instantly, but show input and a button
1844 if( fv_player_editor_conf.frontend ) {
1845 console.log( 'is_playlist_hero_editing', is_playlist_hero_editing );
1846 if( is_playlist_hero_editing ) {
1847 $('#playlist-hero .fv-player-editor-notice.notice-use-ui').show();
1848 } else {
1849 playlist_hero_show();
1850 }
1851
1852 } else {
1853 playlist_item_add();
1854 }
1855
1856 return false;
1857 });
1858
1859 $doc.on('click', '.playlist_edit', function() {
1860 if ( !jQuery(this).hasClass('disabled') ) {
1861 playlist_show();
1862
1863 reload_preview( current_video_to_edit );
1864 }
1865
1866 return false;
1867 });
1868
1869 // prevent closing of the overlay if we have unsaved data
1870 // unfortunately there is no event for this which we could use
1871 // TODO: Now we can do this propery since we dropped Colorbox!
1872 $.fn.fv_player_box.oldClose = $.fn.fv_player_box.close;
1873 $.fn.fv_player_box.close = function() {
1874 // don't close editor if we have errors showing, otherwise we'd just overlay them by an infinite loader
1875 if ( has_errors() ) {
1876 return;
1877 }
1878
1879 /*if (is_draft && is_draft_changed && !window.confirm('You have unsaved changes. Are you sure you want to close this dialog and loose them?')) {
1880 return false;
1881 }*/
1882
1883 // TODO: Why !is_unsaved?
1884 if ( ( !is_unsaved || is_saving ) && has_draft_status && !is_fv_player_screen( editor_button_clicked ) ) {
1885 // TODO: change into notification bubble
1886 alert('Your new player was saved as a draft. To reopen it, open the editor again and use the "Pick existing player" button.');
1887 }
1888
1889 // prevent closing if we're still saving the data
1890 if ( ajax_save_this_please || is_saving || is_loading_meta_data ) {
1891 // if we already have the overlay changed, bail out
1892 if (overlay_close_waiting_for_save) {
1893 return;
1894 }
1895
1896 overlay_close_waiting_for_save = true;
1897 $('.fv-wp-flowplayer-notice-small, .fv-player-shortcode-editor-small-spinner').hide();
1898 overlay_show('loading');
1899
1900 // call fv_wp_flowplayer_submit() which will create a repeating task
1901 // that will check for all meta data being loaded,
1902 // so we can auto-close this overlay once that's done
1903 editor_submit();
1904
1905 return;
1906 }
1907
1908 // close the dialog if confirmed
1909 // $.fn.fv_player_box.oldClose();
1910
1911 // reset variables
1912 is_unsaved = true;
1913 has_draft_status = true;
1914 //is_draft_changed = false;
1915
1916 // manually invoke a heartbeat to remove an edit lock immediatelly
1917 if (current_player_db_id > -1 && window.wp && wp.heartbeat && wp.heartbeat.connectNow ) {
1918 // do this asynchronously to allow our cleanup procedures set lock removal data for the next hearbeat
1919 setTimeout(wp.heartbeat.connectNow, 500);
1920 }
1921 }
1922
1923 /*
1924 Loads and displays a list of all players in a dropdown.
1925 */
1926 $doc.on('click', '.copy_player', function() {
1927 // show loader
1928 overlay_show('loading');
1929
1930 debug_log('Running fv_player_db_retrieve_all_players_for_dropdown Ajax.');
1931
1932 $.post(ajaxurl, {
1933 action: 'fv_player_db_retrieve_all_players_for_dropdown',
1934 nonce: fv_player_editor_conf.search_nonce,
1935 }, show_players ).fail( function ( jqXHR, textStatus, errorThrown ) {
1936 overlay_show('message', 'An unexpected error has occurred while loading players: <code>' + errorThrown + '</code><br /><br />Please try again.');
1937 });
1938
1939 return false;
1940 });
1941
1942 let do_search = false,
1943 is_searching = false,
1944 search_val = false;
1945
1946 $doc.on('keyup', '#fv-player-editor-copy_player-overlay [name=players_selector]', function() {
1947 search_val = $(this).val();
1948 do_search = true;
1949 });
1950
1951 setInterval( function() {
1952 if( do_search ) {
1953 if( is_searching ) {
1954 // Try again later
1955 return;
1956 }
1957
1958 $.post(ajaxurl, {
1959 action: 'fv_player_db_retrieve_all_players_for_dropdown',
1960 nonce: fv_player_editor_conf.search_nonce,
1961 search: search_val
1962 }, show_players ).fail( function ( jqXHR, textStatus, errorThrown ) {
1963 overlay_show('message', 'An unexpected error has occurred while searching in player data: <code>' + errorThrown + '</code><br /><br />Please try again.');
1964 });
1965
1966 is_searching = true;
1967 do_search = false;
1968 }
1969 }, 1000 );
1970
1971 function show_players(json_data) {
1972 if( !json_data.success ) {
1973 overlay_show('message', json_data.data );
1974 return;
1975 }
1976
1977 is_searching = false;
1978
1979 let overlay = overlay_show('copy_player'),
1980 list = overlay.find('.attachments'),
1981 button = overlay.find('.button-primary');
1982
1983 button.prop( 'disabled', true );
1984 list.html('');
1985
1986 for (var i in json_data.players) {
1987 let data = json_data.players[i],
1988 image = $( data.thumbs[0] ).find('img'),
1989 player_name = data.player_name || data.video_titles.join( ', ' );
1990
1991 image.removeAttr('width');
1992
1993 let item = $('<li class="attachment"><div class="attachment-preview js--select-attachment type-video subtype-mp4 landscape fullsize"><div class="thumbnail"><div class="filename hidden"><div></div></div></div></div><button type="button" class="check" tabindex="0"><span class="media-modal-icon"></span><span class="screen-reader-text">Deselect</span></button></li>');
1994
1995 item.find('.thumbnail').append( image );
1996
1997 item.find('.thumbnail .filename > div').html( player_name );
1998 if( image.length == 0 ) {
1999 item.find('.thumbnail .filename').removeClass('hidden');
2000 }
2001 item.attr( 'data-details', JSON.stringify( data ) );
2002
2003 list.append( item );
2004 }
2005
2006 show_players_resize();
2007 }
2008
2009 $( window ).on( 'resize', show_players_resize );
2010
2011 function show_players_resize() {
2012 let player_browser = $( '#fv-player-editor-copy_player-overlay .attachments-browser'),
2013 player_browser_list = player_browser.find( '.attachments' ),
2014 idealColumnWidth = $( window ).width() < 640 ? 135 : 150;
2015
2016 player_browser.attr( 'data-columns', Math.min( Math.round( player_browser_list.width() / idealColumnWidth ), 12 ) || 1 );
2017 }
2018 });
2019
2020
2021
2022 /**
2023 * Initializes shortcode, removes playlist items, hides elements, figures out
2024 * which actual field is edited - post editor, widget, etc.
2025 */
2026 function editor_init() {
2027 // if error / message overlay is visible, hide it
2028 overlay_hide();
2029
2030 remove_notices();
2031
2032 jQuery('#fv_wp_flowplayer_field_player_name').show();
2033
2034 jQuery('#player_id_top_text').html('');
2035
2036 /**
2037 * Get the active post editor instance
2038 */
2039
2040 // is there a Custom Video field or Gutenberg field next to the button?
2041 var field = $(editor_button_clicked).parents('.fv-player-editor-wrapper, .fv-player-gutenberg').find('.fv-player-editor-field'),
2042 elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ),
2043 widget = jQuery('#widget-widget_fvplayer-'+widget_id+'-text');
2044
2045 if( fv_player_editor_conf.field_selector ){
2046 var custom_field_selector = jQuery(fv_player_editor_conf.field_selector)
2047
2048 // If the pre-configured field was not failed it's a big deal!
2049 if( !custom_field_selector.length ){
2050 alert( 'FV Player Editor: Field '+fv_player_editor_conf.field_selector+' not found!' );
2051 }
2052 editor_content = custom_field_selector.val();
2053
2054 // No need for insert button
2055 insert_button_toggle(false);
2056
2057 } else if( field.length ) {
2058 if (field[0].tagName != 'TEXTAREA' && !field.hasClass('attachement-shortcode')) {
2059 field = field.find('textarea').first();
2060 }
2061
2062 editor_content = jQuery(field).val();
2063
2064 } else if( elementor_field.length ) {
2065 editor_content = elementor_field.val();
2066
2067 } else if( widget.length ){
2068 editor_content = widget.val();
2069 } else if( document.querySelector('.CodeMirror') && typeof(CodeMirror) !== 'undefined' ) {
2070 instance_code_mirror = document.querySelector('.CodeMirror').CodeMirror;
2071 } else if( typeof(FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length){
2072 editor_content = jQuery('#content:not([aria-hidden=true])').val();
2073 } else if( typeof tinymce !== 'undefined' && typeof tinymce.majorVersion !== 'undefined' && typeof tinymce.activeEditor !== 'undefined' && tinymce.majorVersion >= 4 ){
2074 instance_tinymce = tinymce.activeEditor;
2075 } else if( typeof tinyMCE !== 'undefined' ) {
2076 instance_tinymce = tinyMCE.getInstanceById('content');
2077 } else if(typeof(FCKeditorAPI) !== 'undefined' ){
2078 instance_fp_wysiwyg = FCKeditorAPI.GetInstance('content');
2079 }
2080
2081 reset_preview();
2082
2083 jQuery('.fv-player-tab-video-files [data-playlist-item]').each( function(i,e) {
2084 if( i == 0 ) return;
2085 jQuery(e).remove();
2086 } );
2087
2088 jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').each( function(i,e) {
2089 if( i == 0 ) return;
2090 jQuery(e).remove();
2091 } );
2092
2093 jQuery('.fv-player-tab-subtitles').html(template_subtitles_tab);
2094 jQuery('.fv_wp_flowplayer_field_subtitles_lang').val(0);
2095
2096 /**
2097 * TABS
2098 */
2099 jQuery('#fv-player-shortcode-editor a[data-tab=fv-player-tab-playlist]').hide();
2100 jQuery('#fv-player-shortcode-editor a[data-tab=fv-player-tab-video-files]').trigger('click');
2101 jQuery('.nav-tab').show;
2102
2103 reset_editor_ids();
2104
2105 editing_video_details = true;
2106
2107 $el_editor
2108 .addClass( 'is-singular' )
2109 .addClass( 'is-singular-active' )
2110 .removeClass( 'is-playlist-active' )
2111 .removeClass( 'is-playlist' );
2112
2113 //hide empy tabs hide tabs
2114 jQuery('.fv-player-tab-playlist').hide();
2115 jQuery('.fv-player-playlist-item-title').html('');
2116 jQuery('.fv-player-tab-video-files [data-playlist-item]').show();
2117
2118 jQuery('.playlist_edit').html(jQuery('.playlist_edit').data('create')).removeClass('button-primary').addClass('button');
2119
2120 tabs_refresh();
2121
2122 playlist_buttons_disable(false);
2123 playlist_buttons_toggle(true);
2124
2125 set_embeds('');
2126
2127 el_preview_target.html('');
2128
2129 if( typeof(fv_player_shortcode_editor_ajax) != "undefined" ) {
2130 fv_player_shortcode_editor_ajax.abort();
2131 }
2132
2133 $doc.trigger('fv-player-editor-init');
2134
2135 hide_inputs();
2136
2137 $el_editor.show();
2138 }
2139
2140 /**
2141 * Checks all the input fields and created the JavaScript object.
2142 * Works when saving and also previewing.
2143 */
2144 function build_ajax_data( give_it_all ) {
2145 var
2146 $tabs = $el_editor.find('.fv-player-tab'),
2147 regex = /((fv_wp_flowplayer_field_|fv_wp_flowplayer_hlskey|fv_player_field_ppv_)[^ ]*)/g,
2148 data = {'video_meta' : {}, 'player_meta' : {}},
2149 end_of_playlist_action = jQuery('#fv_wp_flowplayer_field_end_actions').val(),
2150 single_video_showing = !give_it_all && editing_video_details;
2151
2152 // special processing for end video actions
2153 if (end_of_playlist_action && end_of_playlist_action != 'Nothing') {
2154 switch (end_of_playlist_action) {
2155 case 'redirect':
2156 data['fv_wp_flowplayer_field_end_action_value'] = jQuery('#fv_wp_flowplayer_field_redirect').val();
2157 break;
2158 case 'popup':
2159 data['fv_wp_flowplayer_field_end_action_value'] = jQuery('#fv_wp_flowplayer_field_popup_id').val();
2160 break;
2161 case 'email_list':
2162 data['fv_wp_flowplayer_field_end_action_value'] = jQuery('#fv_wp_flowplayer_field_email_list').val();
2163 break;
2164 }
2165 }
2166
2167 // add playlist name
2168 data['fv_wp_flowplayer_field_player_name'] = jQuery('#fv_wp_flowplayer_field_player_name').val();
2169
2170 // add post ID manually here, as it's a special meta key
2171 insertUpdateOrDeletePlayerMeta({
2172 data: data,
2173 meta_section: 'player',
2174 meta_key: 'post_id',
2175 element: jQuery('#fv_wp_flowplayer_field_post_id')[0],
2176 handle_delete: false
2177 });
2178
2179 // trigger meta data save events, so we get meta data from different
2180 // plugins included as we post
2181 jQuery(document).trigger('fv_flowplayer_player_meta_save', [data, $tabs]);
2182
2183 $tabs.each(function() {
2184 var
2185 $tab = jQuery(this),
2186 is_videos_tab = $tab.hasClass('fv-player-tab-video-files'),
2187 is_subtitles_tab = $tab.hasClass('fv-player-tab-subtitles'),
2188 $input_groups = ((is_videos_tab || is_subtitles_tab) ? $tab.find('[data-playlist-item]') : $tab.find('input, select, textarea')),
2189 save_index = -1;
2190
2191 // prepare video and subtitles data, which are duplicated through their input names
2192 if (is_videos_tab) {
2193 data['videos'] = {};
2194 } else if (is_subtitles_tab) {
2195 data['video_meta']['subtitles'] = {};
2196 data['video_meta']['transcript_src'] = {};
2197 }
2198
2199 // iterate over all tables in tabs
2200 $input_groups.each(function() {
2201 // only videos, subtitles tabs have tables, so we only need to search for their inputs when working with those
2202 var
2203 $inputs = ((is_videos_tab || is_subtitles_tab) ? jQuery(this).find('input, select, textarea').not('.no-data') : jQuery(this).not('.no-data') ),
2204 table_index = jQuery(this).data('index');
2205 save_index++;
2206
2207 $inputs.each(function() {
2208 var
2209 $this = jQuery(this),
2210 m;
2211
2212 while ((m = regex.exec(this.name)) !== null) {
2213 // This is necessary to avoid infinite loops with zero-width matches
2214 if (m.index === regex.lastIndex) {
2215 regex.lastIndex++;
2216 }
2217 // let plugins update video meta, if applicable
2218 jQuery(document).trigger('fv_flowplayer_video_meta_save', [data, save_index, this]);
2219 // videos tab
2220 if (is_videos_tab) {
2221 if (!data['videos'][save_index]) {
2222 data['videos'][save_index] = {
2223 id: jQuery('.fv-player-playlist-item[data-index=' + table_index + ']').data('id_video')
2224 };
2225 }
2226
2227 // check for a meta field
2228 if (check_for_video_meta_field(m[1])) {
2229 // prepare HLS data, if not prepared yet
2230 if (!data['video_meta']['video']) {
2231 data['video_meta']['video'] = {};
2232 }
2233
2234 if (!data['video_meta']['video'][save_index]) {
2235 data['video_meta']['video'][save_index] = {};
2236 }
2237
2238 insertUpdateOrDeleteVideoMeta({
2239 data: data,
2240 meta_section: 'video',
2241 meta_key: get_field_name(m[1]),
2242 meta_index: save_index,
2243 element: this
2244 });
2245 } else {
2246 data['videos'][save_index][m[1]] = map_input_value($this);
2247 }
2248 }
2249
2250 // subtitles tab, subtitles inputs
2251 else if (is_subtitles_tab) {
2252
2253 // TODO: Adding field in PHP with languages => true should add to this array:
2254 let video_meta_languages = {
2255 subtitles: [],
2256 transcript_src: [],
2257 }
2258
2259 $.each( video_meta_languages, function( k, v ) {
2260 if( $this.attr('name') == 'fv_wp_flowplayer_field_' + k ) {
2261 if (!data['video_meta'][ k ][save_index]) {
2262 data['video_meta'][ k ][save_index] = [];
2263 }
2264
2265 let parent = $this.closest( '.components-base-control__field' ),
2266 lang = parent.find( '[name=fv_wp_flowplayer_field_' + k + '_lang]' );
2267
2268 // jQuery-select the SELECT element when we get an INPUT, since we need to pair them
2269 if ( $this[0].nodeName == 'INPUT') {
2270 data['video_meta'][ k ][save_index].push({
2271 code : lang.val(),
2272 file : $this.val(),
2273 id: parent.data('id_videometa')
2274 });
2275 }
2276 }
2277
2278 });
2279 }
2280
2281 // all other tabs
2282 else {
2283 let player_attribute_value = map_input_value($this);
2284
2285 if (check_for_player_meta_field(m[1])) {
2286 // meta data input
2287 insertUpdateOrDeletePlayerMeta({
2288 data: data,
2289 meta_section: 'player',
2290 meta_key: get_field_name(m[1]),
2291 element: this,
2292 handle_delete: false
2293 });
2294 } else {
2295 // Is there a default value for the setting?
2296 let field = m[1].replace(/fv_wp_flowplayer_field_/, '');
2297 if( typeof(window.fv_player_editor_defaults[field]) != "undefined" ) {
2298 // Is it the same? Do not save.
2299 if( window.fv_player_editor_defaults[field] == this.checked ) {
2300 continue;
2301
2302 // Should it be off while default is on? Save literal false
2303 } else if( window.fv_player_editor_defaults[field] && !this.checked ) {
2304 player_attribute_value = 'false';
2305 }
2306 }
2307
2308 // ordinary player attribute
2309 data[m[1]] = player_attribute_value;
2310 }
2311 }
2312 }
2313 });
2314 });
2315 });
2316
2317 // remove any empty videos, i.e. without a source
2318 // this is used when loading data from DB to avoid previewing an empty video that's in editor by default
2319 if (data['videos']) {
2320 var
2321 data_videos_new = {},
2322 x = 0;
2323
2324 for (var i in data['videos']) {
2325 if (data['videos'][i]['src'] || data['videos'][i]['src1'] || !data['videos'][i]['src2']) {
2326 // if we should show preview of a single video only, add that video here,
2327 // otherwise add all videos here
2328 if (!single_video_showing || x == current_video_to_edit) {
2329 data_videos_new[x++] = data['videos'][i];
2330 } else {
2331 x++;
2332 }
2333 }
2334 }
2335
2336 data['videos'] = data_videos_new;
2337 }
2338
2339 // add player ID and deleted elements for a DB update
2340 data['update'] = current_player_db_id;
2341 data['deleted_videos'] = deleted_videos.join(',');
2342 data['deleted_video_meta'] = deleted_video_meta.join(',');
2343 data['deleted_player_meta'] = deleted_player_meta.join(',');
2344
2345 return data;
2346 }
2347
2348 function debug_log( message, details ) {
2349 store_debug_log.push( message );
2350
2351 console.log( 'FV Player Editor: '+message);
2352 if( details ) {
2353 store_debug_log.push( details );
2354
2355 console.log(details);
2356 }
2357 }
2358
2359 function debug_time( time ) {
2360 return Math.round( 100 * ( performance.now() - time ) / 1000 ) / 100 + ' seconds';
2361 }
2362
2363 /**
2364 * Closing the editor
2365 * * remove hidden tags from post editor
2366 * * updates the wp-admin -> FV Player screen
2367 * * sets data for WordPress Heartbeat to unlock the player
2368 * * calls editor_init() for editor clean-up
2369 */
2370 function editor_close() {
2371 // don't close editor if we have errors showing, otherwise we'd just overlay them by an infinite loader
2372 if ( has_errors() ) {
2373 return;
2374 }
2375
2376 // remove TinyMCE hidden tags and other similar tags which aids shortcode editing
2377 // to prevent opening the same player over and over
2378 editor_content = editor_content.replace(fv_wp_flowplayer_re_insert,'');
2379 editor_content = editor_content.replace(fv_wp_flowplayer_re_edit,'');
2380 editor_content = editor_content.replace(/#fvp_placeholder#/, '');
2381 editor_content = editor_content.replace(/#fvp_codemirror_placeholder#/, '');
2382 set_post_editor_content(editor_content);
2383
2384 // this variable needs to be reset here and not in editor_init
2385 set_current_video_to_edit( -1 );
2386
2387 // check if code mirror is active and if so, focus on it & restore cursor position
2388 if( instance_code_mirror && instance_code_mirror_cursor_last ) {
2389 // run after everything else is done
2390 setTimeout(function() {
2391 instance_code_mirror.focus();
2392 instance_code_mirror.setCursor( instance_code_mirror_cursor_last, 0 );
2393 instance_code_mirror_cursor_last = false;
2394 },0);
2395 }
2396
2397 if ( !is_fv_player_screen(editor_button_clicked) ) {
2398 // todo: what it the point of this call being made?
2399 // TODO: Perhaps to ensure the temporary strings in editor are removed?
2400 //set_post_editor_content( editor_content.replace( fv_wp_flowplayer_re_insert, '' ) );
2401
2402 // trigger update for the FV Player Custom Videos/Meta Box and Gutenberg field for preview refresh purposes
2403 var
2404 $editor_button_clicked = $(editor_button_clicked),
2405 $fv_player_custom_meta_box = $editor_button_clicked.parents('.fv-player-editor-wrapper').find('.fv-player-editor-field'),
2406 $fv_player_gutenberg = $editor_button_clicked.parents('.fv-player-gutenberg');
2407
2408 if ( $fv_player_custom_meta_box.length ) {
2409 $fv_player_custom_meta_box.trigger('fv_flowplayer_shortcode_insert');
2410 }
2411
2412 // Update the Gutenberg fields
2413 if( get_current_player_object().videos && get_current_player_object().videos[0] && fv_player_editor.clientId ) {
2414 var src = get_current_player_object().videos[0].src,
2415 splash = get_current_player_object().videos[0].splash,
2416 title = get_current_player_object().videos[0].title,
2417 timeline_previews = get_playlist_video_meta_value( 'timeline_previews', 0 ),
2418 hlskey = get_playlist_video_meta_value( 'hls_hlskey', 0 );
2419
2420 wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes(fv_player_editor.clientId, { src: src, splash: splash, title: title, timeline_previews: timeline_previews, hls_hlskey: hlskey, player_id: current_player_db_id } );
2421 }
2422
2423 // Refresh the Elementor Widget preview
2424 let elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' );
2425 if ( elementor_field.length ) {
2426 elementor_field.trigger( 'input' );
2427 }
2428
2429 } else if( current_player_db_id > 0 ) {
2430
2431 // Append or update player row in wp-admin -> FV Player
2432 if( fv_player_editor_conf.is_fv_player_screen ) {
2433 var playerRow = $('#the-list span[data-player_id="' + current_player_db_id + '"]')
2434 if( playerRow.length == 0 ) {
2435 var firstRow = $('#the-list tr:first'),
2436 newRow = firstRow.clone();
2437
2438 newRow.find('td').html('');
2439 playerRow = newRow.find('td').eq(0);
2440
2441 firstRow.before( newRow )
2442 }
2443
2444 jQuery.post( ajaxurl, {
2445 action : 'fv_player_table_new_row',
2446 nonce : fv_player_editor_conf.table_new_row_nonce,
2447 playerID : current_player_db_id
2448 }, function(response) {
2449 playerRow.closest('tr').replaceWith( $(response).find('#the-list tr') );
2450 });
2451
2452 playerRow.append('&nbsp; <div class="fv-player-shortcode-editor-small-spinner">&nbsp;</div>');
2453
2454 // Update the player on the wp-admin -> Posts, Pages or CPT screen
2455 } else if( fv_player_editor_conf.is_edit_posts_screen ) {
2456
2457 // Existing player
2458 let target_el = jQuery('.fv-player-edit[data-player_id='+current_player_db_id+']'),
2459 args = {
2460 action : 'fv_player_edit_posts_cell',
2461 nonce : fv_player_editor_conf.edit_posts_cell_nonce,
2462 playerID : current_player_db_id
2463 };
2464
2465 // New player, load from the new post meta
2466 if ( target_el.length === 0 && current_post_id ) {
2467 target_el = jQuery('.fv-player-edit[data-post-id='+current_post_id+']');
2468 args.post_id = current_post_id;
2469 args.meta_key = current_post_meta_key;
2470 }
2471
2472 target_el.find('.fv_player_splash_list_preview').append('<div class="fv-player-shortcode-editor-small-spinner">&nbsp;</div>');
2473
2474 jQuery.post( ajaxurl, args, function(response) {
2475 if ( response ) {
2476 target_el.replaceWith( response );
2477 }
2478 });
2479
2480 }
2481
2482 }
2483
2484 // we need to do this now to make sure Heartbeat gets the correct data
2485 if (current_player_db_id > 0 ){
2486 edit_lock_removal[current_player_db_id] = 1;
2487 current_player_db_id = 0;
2488
2489 debug_log( 'editor_close current_player_db_id = 0' );
2490 }
2491
2492 if ( fv_player_editor_conf.on_close ) {
2493 fv_player_editor_conf.on_close();
2494 }
2495
2496 editor_init();
2497
2498 }
2499
2500
2501 /**
2502 * removes previous values from editor
2503 * fills new values from shortcode
2504 *
2505 * @param {int} selected_player_id Optional, force load of specified player ID using "Pick existing player"
2506 */
2507 function editor_open( selected_player_id ) {
2508 if( !selected_player_id ) {
2509 editor_init();
2510 }
2511
2512 instance_code_mirror_cursor_last = false;
2513
2514 // remove any DB data IDs that may be left in the form
2515 $el_editor.find('[data-id]').removeData('id').removeAttr('data-id');
2516 $el_editor.find('[data-id_video]').removeData('id_video').removeAttr('data-id_video');
2517 $el_editor.find('[data-id_videometa]').removeData('id_videometa').removeAttr('data-subtitles');
2518
2519 // fire up editor reset event, so plugins can clear up their data IDs as well
2520 $doc.trigger('fv_flowplayer_player_editor_reset');
2521
2522 // reset content of any input fields, except what has .extra-field
2523 $el_editor.find("input:not(.extra-field), textarea").each( function() {
2524 $(this)
2525 .val( '' )
2526 .prop( 'checked', false )
2527 .trigger( 'change' );
2528 } );
2529
2530 $el_editor.find('select:not([multiple])').prop('selectedIndex',0); // select first index, ignore multiselect - let it be handled separately
2531
2532 $el_editor.find(".fv_player_field_insert-button").text( 'Insert' );
2533
2534 if( window.fv_player_editor_defaults ) {
2535 jQuery.each( window.fv_player_editor_defaults, function(k,v) {
2536 var checkbox = $el_editor.find('[name=fv_wp_flowplayer_field_'+k+'][type=checkbox]');
2537 if( checkbox.length ) {
2538 checkbox.prop( 'checked', !!v ).trigger( 'change' );
2539
2540 var wrap = checkbox.closest('.components-form-toggle');
2541 wrap.toggleClass( 'is-checked is-default', !!v );
2542 }
2543 } );
2544 }
2545
2546 var
2547 field = $(editor_button_clicked).parents('.fv-player-editor-wrapper, .fv-player-gutenberg').find('.fv-player-editor-field'),
2548 clientId = $(editor_button_clicked).parents('.fv-player-editor-wrapper, .fv-player-gutenberg').find('.fv-player-gutenberg-client-id').val(),
2549 elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ),
2550 is_elementor = elementor_field.length,
2551 is_gutenberg = $(editor_button_clicked).parents('.fv-player-gutenberg').length,
2552 shortcode = false,
2553 shortcode_parse_fix = false;
2554
2555 if( clientId ) {
2556 fv_player_editor.clientId = clientId;
2557 } else {
2558 fv_player_editor.clientId = null;
2559 }
2560
2561 // if we've got a numeric DB ID passed to this function, use it directly
2562 // but don't replace editor_content, since we'll need that to be actually updated
2563 // rather then set to a player ID
2564 if (selected_player_id) {
2565 debug_log('Loading for player id: '+selected_player_id );
2566
2567 shortcode = 'fvplayer id="' + selected_player_id + '"';
2568
2569 current_post_id = $(editor_button_clicked).data('post-id');
2570 current_post_meta_key = $(editor_button_clicked).data('meta_key');
2571
2572 if( current_post_id || current_post_meta_key ) {
2573 debug_log( 'New player for post #' + current_post_id + ' for ' + current_post_meta_key + ' meta_key.' );
2574 }
2575
2576 } else {
2577
2578 /**
2579 * Load shortcode for the active post editor
2580 */
2581
2582 if( fv_player_editor_conf.field_selector ){
2583 let custom_field_selector = jQuery(fv_player_editor_conf.field_selector)
2584
2585 // If the pre-configured field was not failed it's a big deal!
2586 if( !custom_field_selector.length ){
2587 alert( 'FV Player Editor: Field '+fv_player_editor_conf.field_selector+' not found!' );
2588 }
2589 editor_content = custom_field_selector.val();
2590 shortcode = editor_content;
2591
2592 // Edit button on wp-admin -> FV Player screen
2593 } else if (is_fv_player_screen_edit(editor_button_clicked)) {
2594 current_player_db_id = $(editor_button_clicked).data('player_id');
2595
2596 current_post_id = $(editor_button_clicked).data('post-id');
2597 current_post_meta_key = $(editor_button_clicked).data('meta_key');
2598
2599 if( current_post_id || current_post_meta_key ) {
2600 debug_log( 'New player for post #' + current_post_id + ' for ' + current_post_meta_key + ' meta_key.' );
2601
2602 editor_content = '';
2603
2604 } else {
2605 debug_log('Loading for FV Player screen, player id: '+current_player_db_id );
2606
2607 // create an artificial shortcode from which we can extract the actual player ID later below
2608 editor_content = '[fvplayer id="' + current_player_db_id + '"]';
2609 shortcode = editor_content;
2610 }
2611 }
2612
2613 // Add new button on wp-admin -> FV Player screen
2614 else if (is_fv_player_screen_add_new(editor_button_clicked)) {
2615 debug_log('Loading for FV Player screen, new player' );
2616
2617 // create empty shortcode for Add New button on the list page
2618 editor_content = '';
2619 shortcode = '';
2620
2621 }
2622
2623 // custom Field or Widget
2624 else if (field.length || jQuery('#widget-widget_fvplayer-' + widget_id + '-text').length) {
2625 debug_log('Loading for custom field or a widget...');
2626
2627 // this is a horrible hack as it adds the hidden marker to the otherwise clean text field value
2628 // just to make sure the shortcode varible below is parsed properly.
2629 // But it allows some extra text to be entered into the text widget, so for now - ok
2630 if (editor_content.match(/\[/)) {
2631 editor_content = '[<' + helper_tag +' rel="FCKFVWPFlowplayerPlaceholder">&shy;</' + helper_tag + '>' + editor_content.replace('[', '') + '';
2632 } else {
2633 editor_content = '<' + helper_tag + ' rel="FCKFVWPFlowplayerPlaceholder">&shy;</' + helper_tag + '>' + editor_content + '';
2634 }
2635
2636 } else if ( is_elementor ) {
2637 debug_log( 'Loading for Elementor Widget...', editor_content );
2638
2639 // CodeMirror
2640 } else if( instance_code_mirror ) {
2641 debug_log('Loading for CodeMirror...' );
2642
2643 // get content
2644 editor_content = instance_code_mirror.getDoc().getValue();
2645
2646 // get cursor position
2647 var position = instance_code_mirror.getDoc().getCursor(),
2648 line = position.line,
2649 ch = position.ch,
2650 line_value = instance_code_mirror.getDoc().getLine(line);
2651
2652 // save cursor position
2653 instance_code_mirror_cursor_last = {line: line, ch: ch};
2654
2655 // look for start of shortcode
2656 var shotcode_pattern = new RegExp(/\[fvplayer[^\[\]]*]?/g);
2657
2658 if( shotcode_pattern.test(line_value) ) {
2659 for( var start = ch; start--; start >= 0 ) {
2660 if( line_value[start] == '[' ) {
2661 var sliced_content = line_value.slice(start);
2662 var matched = sliced_content.match(shotcode_pattern);
2663
2664 if( matched ) {
2665 shortcode = matched[0];
2666 }
2667
2668 break;
2669 } else if( line_value[start] == ']' ) {
2670 break
2671 }
2672 }
2673
2674 } else {
2675 // add placeholder for new editor
2676 instance_code_mirror.getDoc().replaceRange('#fvp_codemirror_placeholder#', {line: line, ch: ch}, {line: line, ch: ch});
2677 editor_content = instance_code_mirror.getDoc().getValue();
2678 }
2679
2680 }
2681 // TinyMCE in Text Mode
2682 else if (typeof (FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length) {
2683 debug_log('Loading for TinyMCE in Text Mode...');
2684
2685 var position = jQuery('#content:not([aria-hidden=true])').prop('selectionStart');
2686
2687 // look for start of shortcode
2688 for (var start = position; start--; start >= 0) {
2689 if (editor_content[start] == '[') {
2690 var sliced_content = editor_content.slice(start);
2691 var matched = sliced_content.match(/^\[fvplayer[^\[\]]*]?/);
2692 // found the shortcode!
2693 if (matched) {
2694 shortcode = matched[0];
2695 }
2696
2697 break;
2698 } else if (editor_content[start] == ']') {
2699 break
2700 }
2701 }
2702 // TODO: It would be better to use #fv_player_editor_{random number}# and remember it for the editing session
2703 editor_content = editor_content.slice(0, position) + '#fvp_placeholder#' + editor_content.slice(position);
2704 }
2705
2706 // Foliopress WYSIWYG
2707 else if (
2708 instance_fp_wysiwyg && (
2709 instance_tinymce == undefined || (typeof tinyMCE !== 'undefined' && tinyMCE.activeEditor.isHidden())
2710 )
2711 ) {
2712 debug_log('Loading for Foliopress WYSIWYG...' );
2713
2714 editor_content = instance_fp_wysiwyg.GetHTML();
2715 if (editor_content.match(fv_wp_flowplayer_re_insert) == null) {
2716 instance_fp_wysiwyg.InsertHtml('<' + fvwpflowplayer_helper_tag + ' rel="FCKFVWPFlowplayerPlaceholder">&shy;</' + fvwpflowplayer_helper_tag + '>');
2717 editor_content = instance_fp_wysiwyg.GetHTML();
2718 }
2719
2720 } else if( instance_tinymce ) {
2721 debug_log('Loading for TinyMCE in Visual Mode...' );
2722
2723 // TinyMCE in Visual Mode
2724 editor_content = instance_tinymce.getContent();
2725 instance_tinymce.settings.validate = false;
2726 if (editor_content.match(fv_wp_flowplayer_re_insert) == null) {
2727 var tags = ['b', 'span', 'div'];
2728 for ( let i in tags) {
2729 instance_tinymce.execCommand('mceInsertContent', false, '<' + tags[i] + ' data-mce-bogus="1" rel="FCKFVWPFlowplayerPlaceholder"></' + tags[i] + '>');
2730 editor_content = instance_tinymce.getContent();
2731
2732 fv_wp_flowplayer_re_edit = new RegExp('\\[f[^\\]]*?<' + tags[i] + '[^>]*?rel="FCKFVWPFlowplayerPlaceholder"[^>]*?>.*?</' + tags[i] + '>.*?[^\]\\]', "mi");
2733 fv_wp_flowplayer_re_insert = new RegExp('<' + tags[i] + '[^>]*?rel="FCKFVWPFlowplayerPlaceholder"[^>]*?>.*?</' + tags[i] + '>', "gi");
2734
2735 if (editor_content.match(fv_wp_flowplayer_re_insert)) {
2736 break;
2737 }
2738
2739 }
2740
2741 }
2742 instance_tinymce.settings.validate = true;
2743 } else {
2744 editor_content = '';
2745
2746 }
2747
2748 if( !shortcode ){
2749 let content = editor_content.replace(/\n/g, '\uffff');
2750
2751 if ( is_elementor || is_gutenberg ) {
2752 shortcode = content;
2753
2754 } else {
2755 let match = content.match( fv_wp_flowplayer_re_edit );
2756 if( match ) {
2757 shortcode = match[0];
2758 }
2759 }
2760 }
2761
2762 }
2763
2764 // remove visual editor placeholders etc.
2765 if (shortcode) {
2766 shortcode = shortcode
2767 .replace(/^\[|]+$/gm, '')
2768 .replace(fv_wp_flowplayer_re_insert, '')
2769 .replace(/\\'/g, '&#039;');
2770 }
2771
2772 if( shortcode ) {
2773 debug_log('Loading shortcode: '+shortcode );
2774
2775 // check for new, DB-based player shortcode
2776 var result = /fvplayer.* id="([\d,]+)"/g.exec(shortcode);
2777 if (result !== null) {
2778 shortcode_parse_fix = shortcode
2779 .replace(/(popup|ad)='[^']*?'/g, '')
2780 .replace(/(popup|ad)="(.*?[^\\\\/])"/g, '');
2781
2782 shortcode_remains = shortcode_parse_fix.replace( /^\S+\s*?/, '' );
2783
2784 // DB-based player, create a "wait" overlay
2785 overlay_show('loading');
2786
2787 // store player ID into fv_player_conf, so we can keep sending it
2788 // in WP heartbeat
2789 current_player_db_id = result[1];
2790
2791 debug_log('Loading shortcode player id: '+current_player_db_id );
2792
2793 if (edit_lock_removal[result[1]]) {
2794 delete edit_lock_removal[result[1]];
2795 }
2796
2797 // check if we don't have multiple-playlists shortcode,
2798 // in which case we need to stop and show an error message
2799 if (shortcode.indexOf(',') > -1) {
2800 overlay_show('message', 'Shortcode editor is not available for multiple players shortcode tag.');
2801 return;
2802 }
2803
2804 // stop Ajax saving that might occur from thinking it's a draft taking place
2805 is_unsaved = false;
2806
2807 is_loading = true;
2808
2809 // now load playlist data
2810 // load video data via an AJAX call
2811 debug_log('Running fv_player_db_load Ajax.');
2812
2813 let time_start = performance.now();
2814
2815 fv_player_shortcode_editor_ajax = jQuery.post( ajaxurl + '?fv_player_db_load=' + result[1], {
2816 action : 'fv_player_db_load',
2817 nonce : fv_player_editor_conf.db_load_nonce,
2818 playerID : result[1],
2819 current_video_to_edit: get_playlist_items_count() > 1 ? current_video_to_edit : -1,
2820 }, function(response) {
2821 var vids = response['videos'];
2822
2823 debug_log('Finished fv_player_db_load Ajax in ' + debug_time( time_start ) + '.',response);
2824
2825 if (response) {
2826
2827 if ( response.debug_duplicate_players && response.debug_duplicate_players.length ) {
2828 alert( "FV Player Editor warning:\n\n\
2829 It seems the player #" + result[1] + " which you are about to edit is a duplicate of the player #" + response.debug_duplicate_players.join(', ') + ".\n\n\
2830 Any changes made to the videos will update the other player as well. \
2831 You can also carefully remove the duplicate player by checking which one is actually used in your post.\n\n\
2832 Please also contact FV Player support with the following debug information:\n\n\
2833 " + store_debug_log.join("\n")
2834 );
2835 }
2836
2837 if( response.error ) {
2838 reset_editor_ids();
2839
2840 overlay_show('message', response.error );
2841
2842 // The editor failed to load, it's not locked
2843 edit_lock_removal[current_player_db_id] = 1;
2844
2845 // Prevent autosave notice from appearing
2846 is_unsaved = true;
2847
2848 is_loading = false;
2849 return;
2850 }
2851
2852 let time_start = performance.now();
2853
2854 init_saved_player_fields( result[1] );
2855
2856 // remove everything with index 0 and the initial video placeholder,
2857 // otherwise our indexing & previews wouldn't work correctly
2858 jQuery('[data-index="0"]').remove();
2859 jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').remove();
2860 jQuery('.fv-player-tab-video-files table').remove();
2861
2862 set_embeds(response['embeds']);
2863
2864 current_player_object = response;
2865
2866 // fire the player load event to cater for any plugins listening
2867 $doc.trigger('fv_flowplayer_player_meta_load', [response]);
2868
2869 for (var key in response) {
2870 // put the field value where it belongs
2871 if (key !== 'videos') {
2872 // in case of meta data, proceed with each player meta one by one
2873 if (key == 'meta') {
2874 for ( let i in response[key]) {
2875 set_editor_field(response[key][i]['meta_key'], response[key][i]['meta_value'], response[key][i]['id']);
2876 }
2877 } else {
2878 set_editor_field(key, response[key]);
2879 }
2880 }
2881 }
2882
2883 let new_video_tabs = [],
2884 new_subtitles_tabs = [];
2885
2886 // add videos from the DB
2887 for (var x in vids) {
2888 let
2889 video_meta_languages = {
2890 subtitles: [],
2891 transcript_src: [],
2892 },
2893 video_meta_to_set = [];
2894
2895 // add all subtitles
2896 if (vids[x].meta && vids[x].meta.length) {
2897 for (var m in vids[x].meta) {
2898 let is_languages_meta = false;
2899 $.each( video_meta_languages, function( k, v ) {
2900 if (vids[x].meta[m].meta_key.indexOf( k ) > -1) {
2901 is_languages_meta = true;
2902
2903 // Map subtitles_en to en and subtitles to just empty string
2904 let lang = vids[x].meta[m].meta_key.replace( k + '_', '');
2905 if ( 'subtitles' === lang ) {
2906 lang = '';
2907 }
2908
2909 video_meta_languages[k].push({
2910 lang: lang,
2911 file: vids[x].meta[m].meta_value,
2912 id: vids[x].meta[m].id
2913 });
2914 }
2915 } );
2916
2917 if ( ! is_languages_meta ) {
2918 video_meta_to_set.push(vids[x].meta[m]);
2919 }
2920 }
2921 }
2922
2923 let new_item = playlist_item_add( vids[x], true );
2924 new_video_tabs.push( new_item.video_tab );
2925 new_subtitles_tabs.push( new_item.subtitles_tab );
2926
2927 $.each( video_meta_languages, function( k, v ) {
2928 for (var i in v) {
2929 language_add( k, v[i].file, v[i].lang, new_item.subtitles_tab, v[i].id);
2930 }
2931 } );
2932
2933 // This sets some additional video meta fields, like remove_black_bars
2934 if (video_meta_to_set.length) {
2935 for ( let i in video_meta_to_set) {
2936 set_editor_field( video_meta_to_set[i].meta_key, video_meta_to_set[i].meta_value, video_meta_to_set[i].id, new_item.video_tab );
2937 }
2938 }
2939
2940 // fire up meta load event for this video, so plugins can process it and react
2941 $doc.trigger('fv_flowplayer_video_meta_load', [ x, vids[x].meta, new_item.video_tab, new_item.subtitles_tab ] );
2942 }
2943
2944 // Add all the new video and subtitle tabs to the DOM all at once
2945 jQuery('.fv-player-tab-video-files').append( new_video_tabs );
2946 jQuery('.fv-player-tab-subtitles').append( new_subtitles_tabs );
2947
2948 debug_log( 'Finished populating editor fields for ' + vids.length + ' videos in ' + debug_time( time_start ) + '.' );
2949
2950 // show playlist instead of the "add new video" form
2951 // if we have more than 1 video
2952 if( current_video_to_edit > -1 ) {
2953 playlist_item_show(current_video_to_edit);
2954 } else if ( vids.length > 1 || fv_player_editor_conf.frontend ) {
2955 playlist_show();
2956 } else {
2957 playlist_item_show(0);
2958 }
2959
2960 // if this player is published, mark it as such
2961 has_draft_status = ( response.status == 'draft' );
2962
2963 // Set the current data as previous to let auto-saving detect changes
2964 ajax_save_previous = build_ajax_data(true);
2965
2966 is_loading = false;
2967 }
2968
2969 overlay_hide();
2970
2971 if ( response.html ) {
2972 // auto-refresh preview
2973 el_preview_target.html( response.html )
2974
2975 $doc.trigger('fvp-preview-complete');
2976 }
2977
2978 // show the Insert button, as this is only used when adding a new player into a post
2979 // and using the Pick existing player button, where we need to be able to actually
2980 // insert the player code into the editor
2981 // ... also, keep the Pick existing player button showing, if we decided to choose
2982 // a different player
2983 if ( selected_player_id ) {
2984 insert_button_toggle(true);
2985 copy_player_button_toggle(true);
2986
2987 } else if ( response.status == 'draft' ) {
2988 // show Save / Insert button, as we're still
2989 // in draft mode for this player
2990 insert_button_toggle(true);
2991 fix_save_btn_text();
2992
2993 } else {
2994 insert_button_toggle(false);
2995 copy_player_button_toggle(false);
2996 }
2997
2998 $doc.trigger('fv_player_editor_player_loaded');
2999
3000 $doc.trigger('fv_player_editor_finished');
3001
3002 }).fail( function( jqXHR, textStatus, errorThrown ) {
3003 if ( jqXHR.status == 404 ) {
3004 overlay_show('message', 'The requested player #' + result[1] + ' could not be found.');
3005 } else {
3006 overlay_show('message', 'An unexpected error has occurred while loading player #' + result[1] + ': <code>' + errorThrown + '</code><br /><br />Please try again.');
3007 }
3008
3009 // show the Insert button, as this is only used when adding a new player into a post
3010 // and using the Pick existing player button, where we need to be able to actually
3011 // insert the player code into the editor
3012 // ... also, keep the Pick existing player button showing, if we decided to choose
3013 // a different player
3014 if ( selected_player_id ) {
3015 insert_button_toggle(true);
3016 copy_player_button_toggle(true);
3017 }
3018 });
3019 } else {
3020 debug_log('Loading shortcode without player id...');
3021
3022 // TODO: Check if all the values are set properly, for example controlbar checkbox is not set properly anymore
3023
3024 $doc.trigger('fv-player-editor-non-db-shortcode');
3025 // ordinary text shortcode in the editor
3026 shortcode_parse_fix = shortcode.replace(/(popup|ad)='[^']*?'/g, '');
3027 shortcode_parse_fix = shortcode_parse_fix.replace(/(popup|ad)="(.*?[^\\\\/])"/g, '');
3028 shortcode_remains = shortcode_parse_fix.replace( /^\S+\s*?/, '' );
3029
3030 var srcurl = shortcode_parse_arg( shortcode_parse_fix, 'src' );
3031 var srcurl1 = shortcode_parse_arg( shortcode, 'src1' );
3032 var srcurl2 = shortcode_parse_arg( shortcode, 'src2' );
3033
3034 var srcrtmp = shortcode_parse_arg( shortcode, 'rtmp' );
3035 var srcrtmp_path = shortcode_parse_arg( shortcode, 'rtmp_path' );
3036
3037 var iwidth = shortcode_parse_arg( shortcode_parse_fix, 'width' );
3038 var iheight = shortcode_parse_arg( shortcode_parse_fix, 'height' );
3039
3040 var sad_skip = shortcode_parse_arg( shortcode, 'ad_skip' );
3041 var scontrolbar = shortcode_parse_arg( shortcode, 'controlbar' );
3042 var sautoplay = shortcode_parse_arg( shortcode, 'autoplay' );
3043 var sliststyle = shortcode_parse_arg( shortcode, 'liststyle' );
3044 var sembed = shortcode_parse_arg( shortcode_parse_fix, 'embed' );
3045 var sloop = shortcode_parse_arg( shortcode, 'loop' );
3046 var slive = shortcode_parse_arg( shortcode, 'live' );
3047 var sshare = shortcode_parse_arg( shortcode, 'share', false, shortcode_share_parse_arg );
3048 var sspeed = shortcode_parse_arg( shortcode, 'speed' );
3049 var ssplash = shortcode_parse_arg( shortcode, 'splash' );
3050 var ssplashend = shortcode_parse_arg( shortcode, 'splashend' );
3051 var ssticky = shortcode_parse_arg( shortcode, 'sticky' );
3052
3053 var splaylist_advance = shortcode_parse_arg( shortcode, 'playlist_advance' );
3054
3055 var ssubtitles = shortcode_parse_arg( shortcode, 'subtitles' );
3056 var aSubtitlesLangs = shortcode.match(/subtitles_[a-z][a-z]+/g);
3057 for( let i in aSubtitlesLangs ){ // move
3058 shortcode_parse_arg( shortcode, aSubtitlesLangs[i], false, shortcode_subtitle_parse_arg );
3059 }
3060 if(!aSubtitlesLangs){ // move
3061 subtitle_language_add(false, false );
3062 }
3063
3064 var smobile = shortcode_parse_arg( shortcode, 'mobile' );
3065 var sredirect = shortcode_parse_arg( shortcode, 'redirect' );
3066
3067 var sCaptions = shortcode_parse_arg( shortcode, 'caption' );
3068 var sSplashText = shortcode_parse_arg( shortcode, 'splash_text' );
3069 var sPlaylist = shortcode_parse_arg( shortcode, 'playlist' );
3070
3071 var sad = shortcode_parse_arg( shortcode, 'ad', true );
3072 var iadwidth = shortcode_parse_arg( shortcode, 'ad_width' );
3073 var iadheight = shortcode_parse_arg( shortcode, 'ad_height' );
3074
3075 // TODO: Test if RTMP is shown
3076 if( srcrtmp != null && srcrtmp[1] != null ) {
3077 jQuery(".fv_wp_flowplayer_field_rtmp").val( srcrtmp[1] );
3078 jQuery(".fv_wp_flowplayer_field_rtmp_wrapper").show();
3079 }
3080 if( srcrtmp_path != null && srcrtmp_path[1] != null ) {
3081 jQuery(".fv_wp_flowplayer_field_rtmp_path").val( srcrtmp_path[1] );
3082 jQuery(".fv_wp_flowplayer_field_rtmp_wrapper").show();
3083 }
3084 var playlist_row = jQuery('.fv-player-tab-playlist tbody tr:first')
3085
3086 if( srcurl != null && srcurl[1] != null ) {
3087 get_field( "src" ).val( srcurl[1] ).trigger('change');
3088 }
3089
3090 if( srcurl1 != null && srcurl1[1] != null ) {
3091 get_field( "src1" ).val( srcurl1[1] ).trigger('change');
3092
3093 if( srcurl2 != null && srcurl2[1] != null ) {
3094 get_field( "src2" ).val( srcurl2[1] ).trigger('change');
3095 }
3096
3097 get_field( 'toggle_advanced_settings' ).prop( 'checked', true ).trigger('change');
3098 }
3099
3100 if( srcurl != null && srcurl[1] != null ) {
3101 get_field( "src" ).val( srcurl[1] );
3102 playlist_row.find('.fvp_item_video-filename').text( srcurl[1] );
3103 }
3104
3105 get_field('width').val(iwidth[1] || '');
3106 get_field('height').val(iheight[1] || '');
3107
3108
3109 if( sautoplay != null && sautoplay[1] != null ) {
3110 if (sautoplay[1] == 'true')
3111 get_field("autoplay")[0].selectedIndex = 1;
3112 if (sautoplay[1] == 'false')
3113 get_field("autoplay")[0].selectedIndex = 2;
3114 if (sautoplay[1] == 'muted')
3115 get_field("autoplay")[0].selectedIndex = 3;
3116 }
3117 if( sliststyle != null && sliststyle[1] != null ) {
3118 var objPlaylistStyle = get_field("playlist")[0];
3119 if (sliststyle[1] == 'horizontal') objPlaylistStyle.selectedIndex = 1;
3120 if (sliststyle[1] == 'tabs') objPlaylistStyle.selectedIndex = 2;
3121 if (sliststyle[1] == 'prevnext') objPlaylistStyle.selectedIndex = 3;
3122 if (sliststyle[1] == 'vertical') objPlaylistStyle.selectedIndex = 4;
3123 if (sliststyle[1] == 'slider') objPlaylistStyle.selectedIndex = 5;
3124 if (sliststyle[1] == 'season') objPlaylistStyle.selectedIndex = 6;
3125 if (sliststyle[1] == 'polaroid') objPlaylistStyle.selectedIndex = 7;
3126 if (sliststyle[1] == 'text') objPlaylistStyle.selectedIndex = 8;
3127 }
3128 if( sembed != null && sembed[1] != null ) {
3129 if (sembed[1] == 'true')
3130 get_field("embed")[0].selectedIndex = 1;
3131 if (sembed[1] == 'false')
3132 get_field("embed")[0].selectedIndex = 2;
3133 }
3134 if( smobile != null && smobile[1] != null )
3135 get_field("mobile").val(smobile[1]);
3136
3137 if( ssplash != null && ssplash[1] != null ) {
3138 get_field("splash").val( ssplash[1] ).trigger('change');
3139
3140 var playlist_img = jQuery('<img />')
3141 .attr('width', 120 )
3142 .attr('src', ssplash[1] );
3143
3144 playlist_row.find('.fvp_item_splash').html( playlist_img );
3145 }
3146
3147 var aSubtitles = false;
3148 if( ssubtitles != null && ssubtitles[1] != null ) {
3149 aSubtitles = ssubtitles[1].split(';');
3150 get_field("subtitles").eq(0).val( aSubtitles[0] );
3151 aSubtitles.shift(); // the first item is no longer needed for playlist parsing which will follow
3152 }
3153
3154 if( ssticky != null && ssticky[1] != null ) {
3155 if (ssticky[1] == 'true')
3156 get_field("sticky")[0].selectedIndex = 1;
3157 if (ssticky[1] == 'false')
3158 get_field("sticky")[0].selectedIndex = 2;
3159 }
3160
3161 if( sad != null && sad[1] != null ) {
3162 sad = sad[1].replace(/&#039;/g,'\'').replace(/&quot;/g,'"').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
3163 sad = sad.replace(/&amp;/g,'&');
3164 get_field("overlay").val(sad);
3165
3166 get_field( 'toggle_overlay' ).prop('checked', true).trigger('change');
3167 }
3168
3169 if( iadheight != null && iadheight[1] != null )
3170 get_field("overlay_height").val(iadheight[1]);
3171 if( iadwidth != null && iadwidth[1] != null )
3172 get_field("overlay_width").val(iadwidth[1]);
3173 if( sad_skip != null && sad_skip[1] != null && sad_skip[1] == 'yes' )
3174 get_field("overlay_skip").prop('checked',true).trigger('change');
3175
3176 if( sspeed != null && sspeed[1] != null ) {
3177 if (sspeed[1] == 'buttons')
3178 get_field("speed")[0].selectedIndex = 1;
3179 if (sspeed[1] == 'no')
3180 get_field("speed")[0].selectedIndex = 2;
3181 }
3182 /*
3183 if( ssplashend != null && ssplashend[1] != null && ssplashend[1] == 'show' )
3184 document.getElementById("fv_wp_flowplayer_field_splashend").checked = 1;
3185 if( sloop != null && sloop[1] != null && sloop[1] == 'true' )
3186 document.getElementById("fv_wp_flowplayer_field_loop").checked = 1;
3187 if( sredirect != null && sredirect[1] != null )
3188 document.getElementById("fv_wp_flowplayer_field_redirect").value = sredirect[1];
3189 */
3190
3191 if( sSplashText != null && sSplashText[1] != null ) {
3192 get_field("splash_text").val(sSplashText[1]);
3193 }
3194
3195
3196 /*
3197 * Video end dropdown
3198 */
3199
3200 //TODO: shortcode popup
3201 // get_field("popup")[0].parentNode.style.display = 'none'
3202 var spopup = shortcode_parse_arg( shortcode, 'popup', true );
3203
3204 let end_actions_val = false;
3205 if( sredirect != null && sredirect[1] != null ){
3206 end_actions_val = 2;
3207 get_field("redirect").val( sredirect[1] );
3208
3209 } else if( sloop != null && sloop[1] != null && sloop[1] == 'true' ){
3210 end_actions_val = 3;
3211
3212 } else if( spopup != null && spopup[1] != null ) {
3213 end_actions_val = 4;
3214
3215 spopup = spopup[1].replace(/&#039;/g,'\'').replace(/&quot;/g,'"').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
3216 spopup = spopup.replace(/&amp;/g,'&');
3217
3218 if( spopup.match(/email-[0-9]*/)){
3219 end_actions_val = 6;
3220
3221 get_field("email_list").val(spopup.match(/email-([0-9]*)/)[1]);
3222
3223 } else {
3224 get_field("popup_id").val(spopup);
3225 }
3226
3227 } else if( ssplashend != null && ssplashend[1] != null && ssplashend[1] == 'show' ){
3228 end_actions_val = 5;
3229 }
3230
3231 if( end_actions_val ) {
3232 get_field("end_actions").prop( 'selectedIndex', end_actions_val ).trigger('change');
3233
3234 get_field( 'toggle_end_action' ).prop('checked', true).trigger('change');
3235 }
3236
3237 if( splaylist_advance != null && splaylist_advance[1] != null ) {
3238 let field = get_field("playlist_advance")[0];
3239 if (splaylist_advance[1] == 'true') field.selectedIndex = 1;
3240 if (splaylist_advance[1] == 'false') field.selectedIndex = 2;
3241 }
3242
3243 if( scontrolbar != null && scontrolbar[1] != null ) {
3244 let field = get_field("controlbar")[0];
3245 if (scontrolbar[1] == 'yes' || scontrolbar[1] == 'show' ) field.selectedIndex = 1;
3246 if (scontrolbar[1] == 'no' || scontrolbar[1] == 'hide' ) field.selectedIndex = 2;
3247 }
3248
3249 var aCaptions = false;
3250 if( sCaptions ) {
3251 aCaptions = shortcode_arg_split(sCaptions);
3252
3253 var caption = aCaptions.shift();
3254 get_field("title").val( caption );
3255 if( caption ) {
3256 playlist_row.find('.fvp_item_video-filename').text( caption );
3257 }
3258 }
3259
3260 var aSplashText = false;
3261 if( sSplashText ) {
3262 aSplashText = shortcode_arg_split(sSplashText);
3263
3264 var splash_text = aSplashText.shift();
3265 get_field("splash_text").val( splash_text );
3266 }
3267
3268 if( sPlaylist ) {
3269 // check for all-numeric playlist items separated by commas
3270 // which outlines video IDs from a database
3271 var aPlaylist = sPlaylist[1].split(';');
3272 for ( let i in aPlaylist) {
3273
3274 // TODO: Will this work?
3275 playlist_item_add( {
3276 src: aPlaylist[i],
3277 title: aCaptions[i],
3278 subtitles: aSubtitles[i],
3279 splash_text: aSplashText[i]
3280 } );
3281 }
3282 }
3283
3284 if( jQuery('.fv-fp-subtitles .fv-fp-subtitle:first input.fv_wp_flowplayer_field_subtitles').val() == '' ) {
3285 jQuery('.fv-fp-subtitles .fv-fp-subtitle:first').remove();
3286 }
3287
3288 try {
3289 jQuery(document).trigger('fv_flowplayer_shortcode_parse', [ shortcode_parse_fix, shortcode_remains ] );
3290 } catch(e) {
3291 debug_log('Error: fv_flowplayer_shortcode_parse', e);
3292 }
3293
3294 if (slive != null && slive[1] != null && slive[1] == 'true') {
3295 jQuery("input[name=fv_wp_flowplayer_field_live]").each(function () {
3296 this.checked = 1;
3297 jQuery(this).closest('tr').show();
3298 });
3299 }
3300
3301 if(sPlaylist){
3302 playlist_show();
3303 } else {
3304 playlist_item_show(0);
3305 }
3306
3307 tabs_refresh();
3308
3309 }
3310
3311 store_shortcode_args = {};
3312 always_keep_shortcode_args = {};
3313 for( let i in fv_player_editor_conf.shortcode_args_to_preserve ) {
3314 let value = shortcode_parse_arg( shortcode_parse_fix, fv_player_editor_conf.shortcode_args_to_preserve[i] );
3315 if (value && value[1]) {
3316 store_shortcode_args[fv_player_editor_conf.shortcode_args_to_preserve[i]] = value[1];
3317 }
3318 }
3319
3320 if( store_shortcode_args.length ) {
3321 debug_log('Preserving shortcode args', store_shortcode_args );
3322 }
3323
3324 for( let i in fv_player_editor_conf.shortcode_args_not_db_compatible ) {
3325 let value = shortcode_parse_arg( shortcode_parse_fix, fv_player_editor_conf.shortcode_args_not_db_compatible[i] );
3326 if (value && value[1]) {
3327 always_keep_shortcode_args[fv_player_editor_conf.shortcode_args_not_db_compatible[i]] = value[1];
3328 }
3329 }
3330
3331 if( always_keep_shortcode_args.length ) {
3332 debug_log('Always preserve shortcode args', always_keep_shortcode_args );
3333 }
3334
3335 } else {
3336 debug_log('New player...' );
3337
3338 playlist_item_show(0);
3339
3340 jQuery(document).trigger('fv_flowplayer_shortcode_new');
3341 shortcode_remains = '';
3342 fix_save_btn_text();
3343 }
3344
3345 $doc.trigger('fv_player_editor_finished');
3346 }
3347
3348 /**
3349 * Makes sure that the Save / Insert button is showing the correct text
3350 * based on current context.
3351 */
3352 function fix_save_btn_text() {
3353 // rename insert to save for new playlists if we come from list view
3354 if ( is_fv_player_screen_add_new( editor_button_clicked ) || is_fv_player_screen_edit( editor_button_clicked ) ) {
3355 jQuery('.fv_player_field_insert-button').text('Save');
3356 } else {
3357 // we're in a post / widget, rename save button to Insert
3358 jQuery('.fv_player_field_insert-button').text('Insert');
3359 }
3360 }
3361
3362 /*
3363 * Saving the data
3364 */
3365 function editor_submit() {
3366 // bail out if we're already saving, we're loading meta data or we have errors
3367 if ( ajax_save_this_please || is_saving || is_loading_meta_data || has_errors() ) {
3368 // if we're saving a new player, let's disable the Save button and wait until meta data are loaded
3369 if ( current_player_db_id < 0 ) {
3370 if (is_loading_meta_data) {
3371 insert_button_toggle_disabled(true);
3372
3373 $el_editor.find('.button-primary').text('Saving...');
3374 var checker = setInterval(function() {
3375 if (is_loading_meta_data <= 0) {
3376 clearInterval(checker);
3377 editor_submit(); // call this function again, so we can really save this time
3378 }
3379 }, 500);
3380
3381 return;
3382 }
3383 } else {
3384 // if we're updating a player, just return here if we're loading meta data,
3385 // as that would result in duplicate save - once with and once without meta data
3386 if (is_loading_meta_data) {
3387 return;
3388 }
3389 }
3390
3391 // we have errors, disable the Save button
3392 if ( has_errors() ) {
3393 insert_button_toggle_disabled(true);
3394 return;
3395 }
3396 }
3397
3398 // TODO: Tell people they only have RTMP
3399 var field_rtmp = get_field("rtmp"),
3400 field_rtmp_path = get_field("rtmp_path")
3401
3402 if(
3403 field_rtmp.attr('placeholder') == '' &&
3404 get_field("rtmp_wrapper").is(":visible") &&
3405 (
3406 ( field_rtmp.val() != '' && field_rtmp_path.val() == '' ) ||
3407 ( field_rtmp.val() == '' && field_rtmp_path.val() != '' )
3408 )
3409 ) {
3410 alert('Please enter both server and path for your RTMP video.');
3411 return false;
3412 } else if(
3413 get_field("src").val() == ''
3414 && get_field("rtmp").val() == ''
3415 && get_field("rtmp_path").val() == '') {
3416 alert('Please enter the file name of your video file.');
3417 return false;
3418 }
3419
3420 var ajax_data = build_ajax_data(true);
3421
3422 overlay_show('loading');
3423
3424 var player_was_non_db = (current_player_db_id > -1 ? false : true);
3425
3426 // unmark DB player ID as being currently edited
3427 if ( current_player_db_id > 0 ) {
3428 current_player_db_id = 0;
3429 }
3430
3431 // if player should be in published state, add it into the AJAX data
3432 if ( !has_draft_status ) {
3433 ajax_data['status'] = 'published';
3434 }
3435
3436 debug_log('Running fv_player_db_save Ajax on submit.');
3437
3438 ajax_data = set_control_fields( ajax_data );
3439
3440 let time_start = performance.now();
3441
3442 // save data
3443 // We use ?fv_player_db_save=1 as some people use that to exclude firewall rules
3444 $.ajax({
3445 type: 'POST',
3446 url: ajaxurl + '?action=fv_player_db_save',
3447 data: JSON.stringify( {
3448 data: ajax_data,
3449 nonce: fv_player_editor_conf.edit_nonce,
3450 } ),
3451 contentType: 'application/json',
3452 dataType: 'json',
3453 success: function(response) {
3454
3455 debug_log( 'Finished fv_player_db_save Ajax in ' + debug_time( time_start ) + '.' );
3456
3457 if( response.error ) {
3458 if( response.error && response.fatal_error ) {
3459
3460 debug_log( 'Fatal error in fv_player_db_save: ' + response.fatal_error );
3461
3462 let json_export_data = jQuery('<div/>').text(JSON.stringify(ajax_data)).html();
3463
3464 let overlay = overlay_show('error_saving');
3465 overlay.find('textarea').val( response.error + '\n\nJSON data:\n\n' + $('<div/>').text(json_export_data).html() );
3466
3467 jQuery('#fv_player_copy_to_clipboard').select();
3468
3469 } else {
3470 add_notice( 'error', response.error );
3471
3472 debug_log( 'Error in fv_player_db_save: ' + response.error );
3473 }
3474
3475 el_spinner.hide();
3476 is_saving = false;
3477
3478 return;
3479 }
3480
3481 // player saved, reset draft status
3482 is_unsaved = false;
3483 //is_draft_changed = false;
3484
3485 current_player_db_id = parseInt(response.id);
3486 if( current_player_db_id > 0 ) {
3487 let shortcode_insert = '[fvplayer id="' + current_player_db_id + '"';
3488
3489 // we have extra presentation parameters to keep
3490 if (store_shortcode_args) {
3491 // if this was a non-DB player and is being converted into a DB-player,
3492 // remove all parameters to keep that will go into the DB
3493 var args_to_keep = {};
3494
3495 for (var i in store_shortcode_args) {
3496 if (always_keep_shortcode_args[ i ]) {
3497 args_to_keep[ i ] = store_shortcode_args[ i ];
3498 }
3499 }
3500
3501 store_shortcode_args = args_to_keep;
3502
3503 shortcode_insert += add_shortcode_args( store_shortcode_args );
3504
3505 } else if (always_keep_shortcode_args && player_was_non_db) {
3506 // we have extra parameters to keep that are DB-incompatible
3507 shortcode_insert += add_shortcode_args( always_keep_shortcode_args );
3508
3509 } else {
3510 // simple DB shortcode, no extra presentation parameters
3511 insert_shortcode('[fvplayer id="' + current_player_db_id + '"]');
3512 }
3513
3514 shortcode_insert += ']';
3515
3516 insert_shortcode( shortcode_insert );
3517
3518 if( fv_player_editor.clientId ) {
3519 wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes(fv_player_editor.clientId, { shortcodeContent: shortcode_insert, player_id: current_player_db_id });
3520 }
3521
3522 jQuery(".fv-wordpress-flowplayer-button").fv_player_box.close();
3523
3524 } else {
3525 let json_export_data = jQuery('<div/>').text(JSON.stringify(ajax_data)).html();
3526
3527 let overlay = overlay_show('error_saving');
3528 overlay.find('textarea').val( $('<div/>').text(json_export_data).html() );
3529
3530 jQuery('#fv_player_copy_to_clipboard').select();
3531 }
3532 },
3533 error: function( jqXHR, textStatus, errorThrown ) {
3534 overlay_show('message', 'An unexpected error has occurred while saving player: <code>' + errorThrown + '</code><br /><br />Please try again');
3535
3536 debug_log( 'HTTP Error in fv_player_db_save: ' + errorThrown + ': ' + jqXHR.responseText );
3537 }
3538 });
3539
3540 return;
3541
3542 }
3543
3544 function fv_wp_flowplayer_dialog_resize() {
3545 debug_log('Deprecated fv_wp_flowplayer_dialog_resize() call.');
3546 }
3547
3548 function get_pretty_aspect_ratio( video ) {
3549 let w, h, dividend, divisor, remainder;
3550
3551 // Sanitize input
3552 video.width = parseInt( video.width );
3553 video.height = parseInt( video.height );
3554 video.aspect_ratio = parseFloat( video.aspect_ratio );
3555
3556 if( video.width && video.height ) {
3557 w = video.width;
3558 h = video.height;
3559
3560 } else if( video.aspect_ratio ) {
3561 w = 1;
3562 h = video.aspect_ratio;
3563
3564 } else {
3565 return false;
3566 }
3567
3568 if( h == w ) {
3569 return '1:1';
3570
3571 } else {
3572 if( h > w ) {
3573 dividend = h;
3574 divisor = w;
3575 }
3576
3577 if( w > h ) {
3578 dividend = w;
3579 divisor = h;
3580 }
3581
3582 let gcd = -1,
3583 loop_runs = 0;
3584
3585 while( gcd == -1 ) {
3586 loop_runs++;
3587
3588 // Avoid endless loop, what if...
3589 if( loop_runs > 100 ) {
3590 gcd = divisor;
3591 break;
3592 }
3593
3594 remainder = dividend % divisor;
3595 if( remainder == 0 ){
3596 gcd = divisor;
3597 } else {
3598 dividend = divisor;
3599 divisor = remainder;
3600 }
3601 }
3602
3603 return ( w / gcd ) + ':' + ( h / gcd );
3604 }
3605 }
3606
3607 function insert_button_toggle( show ) {
3608
3609 // Do not show Insert button if player is configured for a set field
3610 if( show && fv_player_editor_conf.field_selector ) {
3611 return;
3612 }
3613
3614 $('.fv_player_field_insert-button').toggle( show );
3615 }
3616
3617 function insert_button_toggle_disabled( disable ) {
3618 var button = $('.fv_player_field_insert-button');
3619 if( disable ) {
3620 button.prop('disabled', true);
3621 } else {
3622 button.prop('disabled', false);
3623 }
3624 }
3625
3626 /**
3627 * Sends new shortcode to post editor
3628 */
3629 function insert_shortcode( shortcode ) {
3630
3631 // do not insert new shortcode if using button on wp-admin -> FV Player
3632 if( is_fv_player_screen(editor_button_clicked) ) {
3633 return;
3634 }
3635
3636 var field = $(editor_button_clicked).parents('.fv-player-editor-wrapper').find('.fv-player-editor-field'),
3637 elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ),
3638 gutenberg = $(editor_button_clicked).parents('.fv-player-gutenberg').find('.fv-player-editor-field'),
3639 widget = jQuery('#widget-widget_fvplayer-'+widget_id+'-text'),
3640 custom_field_selector = jQuery(fv_player_editor_conf.field_selector);
3641 if ( fv_player_editor_conf.on_insert ) {
3642 fv_player_editor_conf.on_insert( shortcode );
3643 return;
3644 }
3645
3646 // Field set by the [fvplayer_editor field="{selector}"]
3647 if( custom_field_selector.length ){
3648 custom_field_selector.val( shortcode );
3649
3650 // is there a Gutenberg field together in wrapper with the button?
3651 } else if( gutenberg.length ) {
3652
3653 // is there a plain text field together in wrapper with the button?
3654 } else if (field.length) {
3655 field.val(shortcode);
3656 // Prevents double event triggering in FV Player Custom Video box
3657 //field.trigger('fv_flowplayer_shortcode_insert', [shortcode]);
3658
3659 } else if ( elementor_field.length ) {
3660 elementor_field.val( shortcode ).trigger( 'input' );
3661
3662 // FV Player in a Widget
3663 } else if( widget.length ){
3664 widget.val( shortcode );
3665 widget.trigger('keyup'); // trigger keyup to make sure Elementor updates the content
3666 widget.trigger('fv_flowplayer_shortcode_insert', [ shortcode ] );
3667
3668 // CodeMirror tab
3669 } else if(instance_code_mirror) {
3670 editor_content = editor_content.replace(/#fvp_codemirror_placeholder#/, shortcode);
3671 set_post_editor_content(editor_content);
3672 } else if (typeof(FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length) {
3673 // editing
3674 if( editor_content.match(/\[.*?#fvp_placeholder#.*?\]/) ) {
3675 editor_content = editor_content.replace(/\[.*?#fvp_placeholder#.*?\]/, shortcode);
3676
3677 //inserting
3678 } else {
3679 editor_content = editor_content.replace(/#fvp_placeholder#/, shortcode);
3680 }
3681 set_post_editor_content(editor_content);
3682
3683 // or are we editing a shortcode in post content?
3684 } else if (editor_content.match(fv_wp_flowplayer_re_edit)) {
3685 editor_content = editor_content.replace(fv_wp_flowplayer_re_edit, shortcode)
3686 set_post_editor_content(editor_content);
3687
3688 // is it a new player instance
3689 } else {
3690
3691 // in existing post content?
3692 if (editor_content != '') {
3693 editor_content = editor_content.replace(fv_wp_flowplayer_re_insert, shortcode)
3694 set_post_editor_content(editor_content);
3695
3696 // in blank post?
3697 } else {
3698 editor_content = shortcode;
3699 send_to_editor(shortcode);
3700 }
3701 }
3702
3703 }
3704
3705 /*
3706 Determines if the button clicked is on wp-admin -> FV Player or wp-admin -> Posts (if using FV Player Video Custom Fields)
3707 */
3708 function is_fv_player_screen(button) {
3709 // TODO: Should use fv_player_editor_conf.is_fv_player_screen || fv_player_editor_conf.is_edit_posts_screen
3710 return is_fv_player_screen_add_new(button) || is_fv_player_screen_edit(button);
3711 }
3712
3713 /*
3714 Determines if the button clicked is Add New on wp-admin -> FV Player
3715 */
3716 function is_fv_player_screen_add_new(button) {
3717 return typeof( $(button).data('add_new') ) != 'undefined';
3718 }
3719
3720 /*
3721 Determines if the button clicked is Edit on wp-admin -> FV Player
3722 */
3723 function is_fv_player_screen_edit(button) {
3724 return (
3725 typeof( $(button).data('player_id') ) != 'undefined' ||
3726 typeof( $(button).data('post-id') ) != 'undefined'
3727 );
3728 }
3729
3730 /*
3731 Determines if the button clicked is Insert/Edit on wp-admin -> Appearance -> Widgets
3732 */
3733 function is_fv_player_widgets(button) {
3734 return button.id.indexOf('widget-widget_fvplayer') > -1;
3735 }
3736
3737 function is_live_stream( video ) {
3738 return !! ( video.src && video.src.match( /\.m3u8|live/ ) );
3739 }
3740
3741 /*
3742 Sets lightbox class once it opens
3743 */
3744 function lightbox_open() {
3745 $("#fv_player_box").addClass("fv-flowplayer-shortcode-editor");
3746 }
3747
3748 function map_checkbox_value( $element ) {
3749 var input_type = $element.get(0).type.toLowerCase(),
3750 is_checked = $element.get(0).checked;
3751
3752 if ( 'checkbox' == input_type ) {
3753 return is_checked ? 'true' : '';
3754 }
3755 }
3756
3757 function map_dropdown_value( dropdown_element ) {
3758 var $valueLessOptions = dropdown_element.find('option:not([value])'),
3759 value = dropdown_element[0].value.toLowerCase(),
3760 index = dropdown_element[0].selectedIndex;
3761
3762 // multiselect element
3763 if(dropdown_element[0].multiple) {
3764 var selected = [],
3765 options = dropdown_element[0].options,
3766 opt;
3767
3768 for (var i=0, iLen=options.length; i<iLen; i++) {
3769 opt = options[i];
3770
3771 // take only selected with value
3772 if (opt.selected && opt.value) {
3773 selected.push(opt.value);
3774 }
3775 }
3776
3777 return selected.length ? selected.join(',') : '';
3778
3779 } else if ( $valueLessOptions.length ) { // at least one option is value-less
3780 // the first one is always default and should be sent as ''
3781 return index === 0 ? '' : value;
3782
3783 } else {
3784 // normal dropdown - all options have a value, return this.value (option's own value)
3785 return value;
3786 }
3787 }
3788
3789 function map_input_type( $el ) {
3790 var input_type = $el.get(0).type.toLowerCase();
3791
3792 if ( 'SELECT' == $el.get(0).nodeName ) {
3793 input_type = 'select';
3794 }
3795
3796 return input_type;
3797 }
3798
3799 function map_input_value( $el ) {
3800 let input_type = map_input_type( $el ),
3801 value = $el.val();
3802
3803 // check for a select without any option values, in which case we'll use their text
3804 if ( 'select' == input_type ) {
3805 value = map_dropdown_value( $el );
3806
3807 } else if ( 'checkbox' == input_type ) {
3808 value = map_checkbox_value( $el );
3809 }
3810
3811 return value;
3812 }
3813
3814 function map_names_to_editor_fields(name) {
3815 var fieldMap = {
3816 'liststyle': 'playlist',
3817 'preroll': 'video_ads',
3818 'postroll': 'video_ads_post'
3819 };
3820
3821 return 'fv_wp_flowplayer_field_' + (fieldMap[name] ? fieldMap[name] : name);
3822 }
3823
3824 function playlist_add_toggle_function( hero_on ) {
3825 let button = $( '.playlist_add' );
3826 button.html( button.data( hero_on ? 'alt-html' : 'html' ) );
3827 }
3828
3829 function playlist_buttons_disable( reason ) {
3830 if( reason ) {
3831 $('.playlist_add, .playlist_edit').addClass('disabled').attr('title', reason);
3832 } else {
3833 $('.playlist_add, .playlist_edit').removeClass('disabled');
3834 }
3835 }
3836
3837 function playlist_buttons_toggle( show ) {
3838 $('.playlist_add, .playlist_edit').css( 'display', show ? 'inline-block' : 'none' );
3839 }
3840
3841 function playlist_hero_hide() {
3842 playlist_add_toggle_function();
3843
3844 $( '#fv-player-editor-playlist .fv-player-editor-playlist-item:last').show();
3845 $( '#playlist-hero' ).hide();
3846
3847 // Reset playlist item count on the hero input box
3848 $( '#playlist-hero [name=hero-src]').data( 'new-item-index', false ).val('');
3849
3850 // Reset playlist item count on the hero Media Library button
3851 $( '#playlist-hero button').data( 'new-item-index', false );
3852
3853 is_playlist_hero_editing = false;
3854 }
3855
3856 function playlist_hero_show() {
3857 is_playlist_hero_editing = true;
3858
3859 playlist_add_toggle_function( true );
3860
3861 $( '#playlist-hero' ).show();
3862 }
3863
3864 // Clicking anywhere else should hide the prompt, the button to add playlist item is excluded too
3865 $( $el_editor ).on( 'click', function( e ) {
3866 if( is_playlist_hero_editing && $( e.target ).closest( '#playlist-hero').length == 0 && !$( e.target ).hasClass( 'playlist_add') ) {
3867 playlist_hero_hide();
3868 }
3869 });
3870
3871 /**
3872 * Adds playlist item
3873 *
3874 * @param {object|string} input Object from FV Player database or legacy shortcode argument
3875 * text which was a comma separated list of URLs
3876 * @param {boolean} skip_dom_add If true, the item will not be added to the DOM and you will need to add it manually.
3877 *
3878 * @return {jQuery} New playlist item.
3879 */
3880 function playlist_item_add( input, skip_dom_add ) {
3881 var new_playlist_item = $(template_playlist_item);
3882 $('.fv-player-tab-playlist #fv-player-editor-playlist').append(new_playlist_item);
3883
3884 var ids = jQuery('.fv-player-tab-playlist [data-index]').map(function() {
3885 return parseInt(jQuery(this).attr('data-index'), 10);
3886 }).get();
3887 var newIndex = Math.max(Math.max.apply(Math, ids) + 1,0);
3888
3889 var current = jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').last();
3890 current.attr('data-index', newIndex);
3891 current.find('.fvp_item_video-filename').text( 'Video ' + (newIndex + 1) );
3892 current.find('.fvp_item_video-duration').text( '' );
3893
3894 let new_item = jQuery( template_video );
3895 new_item.hide().attr('data-index', newIndex);
3896
3897 let new_item_subtitles = jQuery( template_subtitles_tab );
3898 new_item_subtitles.hide().attr('data-index', newIndex);
3899
3900 // Since we are using the virtual DOM, we need to call the change event for the checkboxes manually
3901 if ( skip_dom_add ) {
3902 new_item.on( 'change', '.components-form-toggle input[type=checkbox]', function() {
3903 var wrap = $(this).closest('.components-form-toggle'),
3904 checked = $(this).prop('checked'),
3905 name = $(this).attr('name').replace( /fv_wp_flowplayer_field_/, '' );
3906
3907 checkbox_toggle_worker(wrap, name, checked);
3908 });
3909
3910 // Since we are using the virtual DOM, we need to call the change event for the inputs and selects manually
3911 new_item.on('change', '.components-text-control__input, .components-select-control__input', function() {
3912 var input = jQuery(this),
3913 parent = input.closest('.fv-player-editor-children-wrap'),
3914 name = input.attr('name').replace( /fv_wp_flowplayer_field_/, '' ),
3915 wrap = input.parents( '.fv-player-editor-field-wrap-' + name );
3916
3917 text_and_select_worker( input, parent, name, wrap );
3918 });
3919 }
3920
3921 // processing database input
3922 if( typeof(input) == 'object' ) {
3923 var objVid = input;
3924
3925 new_item.attr('data-id_video', objVid.id);
3926 get_field('src',new_item).val(objVid.src);
3927 get_field('src1',new_item).val(objVid.src1);
3928 get_field('src2',new_item).val(objVid.src2);
3929
3930 get_field('mobile',new_item).val(objVid.mobile).trigger( 'change' );
3931
3932 get_field('rtmp',new_item).val(objVid.rtmp);
3933 get_field('rtmp_path',new_item).val(objVid.rtmp_path);
3934
3935 get_field('title',new_item).val(objVid.title).trigger( 'change' );
3936 get_field('title_hide',new_item).prop( 'checked', objVid.title_hide ).trigger( 'change' );
3937 get_field('splash',new_item).val(objVid.splash);
3938 get_field('splash_text',new_item).val(objVid.splash_text).trigger( 'change' );
3939 get_field('splash_attachment_id',new_item).val(objVid.splash_attachment_id);
3940
3941 get_field('start',new_item).val(objVid.start).trigger( 'change' );
3942 get_field('end',new_item).val(objVid.end).trigger( 'change' );
3943
3944 get_field('toggle_advanced_settings',new_item).prop('checked', objVid.toggle_advanced_settings).trigger('change');
3945
3946 jQuery(objVid.meta).each( function(k,v) {
3947 Object.keys( window.fv_player_editor_fields ).forEach( function( field_name ) {
3948 if ( 'video_meta' == window.fv_player_editor_fields[ field_name].store ) {
3949 if ( v.meta_key == field_name ) {
3950 get_field( field_name, new_item ).val( v.meta_value ).attr('data-id',v.id).trigger( 'change' );
3951 }
3952 }
3953 } );
3954
3955 if( v.meta_key == 'audio' ) get_field('audio',new_item).prop('checked',v.meta_value).attr('data-id',v.id);
3956 });
3957
3958 } else {
3959 new_playlist_item.find('.fvp_item_video-thumbnail').addClass( 'no-img' );
3960 }
3961
3962 // fire up an update event if we're adding an empty template, which means this function is called
3963 // outside of the player meta loading and we should inform plugins that they need to add their own
3964 // video tab content
3965 if (!input) {
3966 $doc.trigger('fv-player-playlist-item-add');
3967 }
3968
3969 hide_inputs();
3970
3971 if ( ! skip_dom_add ) {
3972 jQuery('.fv-player-tab-video-files').append( new_item );
3973 jQuery('.fv-player-tab-subtitles').append( new_item_subtitles );
3974 }
3975
3976 return { video_tab: new_item, subtitles_tab: new_item_subtitles };
3977 }
3978
3979 /*
3980 Show a certain playlist item, it's Video and Subtitles tab
3981 */
3982 function playlist_item_show( new_index ) {
3983 let previous_index = current_video_to_edit;
3984
3985 set_current_video_to_edit( new_index );
3986
3987 editing_video_details = true;
3988
3989 $el_editor
3990 .removeClass( 'is-playlist-active' )
3991 .addClass( 'is-singular-active' );
3992
3993 jQuery('.fv-player-tabs-header .nav-tab').attr('style',false);
3994
3995 $doc.trigger('fv_flowplayer_shortcode_item_switch', [ new_index ] );
3996
3997 $('a[data-tab=fv-player-tab-video-files]').trigger('click');
3998
3999 get_tabs('video-files').hide();
4000 var video_tab = get_tab(new_index,'video-files').show();
4001
4002 if( video_tab.attr('data-id_video') ) {
4003 current_video_db_id = video_tab.attr('data-id_video');
4004 debug_log('current_video_db_id: '+current_video_db_id);
4005 }
4006
4007 get_tabs('subtitles').hide();
4008
4009 var subtitles_tab = get_tab(new_index,'subtitles').show();
4010
4011 if($('.fv-player-tab-playlist [data-index]').length > 1){
4012 $('.fv-player-playlist-item-title').html('Playlist item no. ' + ++new_index);
4013 $('.playlist_edit').html($('.playlist_edit').data('edit'));
4014
4015 $el_editor.addClass( 'is-playlist' );
4016
4017 }else{
4018 $('.playlist_edit').html($('.playlist_edit').data('create'));
4019
4020 $el_editor.addClass( 'is-singular' );
4021 }
4022
4023 // As Flowplayer only lets us set RTMP server for the first video in playlist, prefill it for this new item as well
4024 if(new_index > 1){
4025 get_field('rtmp',video_tab).val( get_field('rtmp',$('.fv-player-tab-video-files table').eq(0) ).val()).attr('readonly',true);
4026 }
4027
4028 $('.fv_wp_flowplayer_field_subtitles_lang, .subtitle_language_add_link').attr('style',false);
4029
4030 tabs_refresh();
4031
4032 if( previous_index != current_video_to_edit && get_playlist_items_count() > 1 ) {
4033 reload_preview( current_video_to_edit );
4034 }
4035
4036 hide_inputs();
4037
4038 $doc.trigger('fv-player-editor-video-opened', [ new_index ] );
4039
4040 /**
4041 * Upgrade text inputs to select boxes for matching fields
4042 */
4043 window.fv_player_editor_fields_with_language.forEach( function( field_name ) {
4044 let fields_with_languages = $( '.fv_wp_flowplayer_field_' + field_name + '_lang', subtitles_tab );
4045
4046 fields_with_languages.each( function() {
4047 let $element = $( this ),
4048 value = $element.val();
4049
4050 $element.replaceWith( '<select class="fv_wp_flowplayer_field_' + field_name + '_lang" name="fv_wp_flowplayer_field_' + field_name + '_lang">' +
4051 '<option value="">' + fv_player_editor_conf.field_languages_default + '</option>' +
4052 Object.keys( fv_player_editor_conf.field_languages ).map( function( lang_code ) {
4053 let html = '<option value="' + lang_code.toLowerCase() + '"';
4054 if ( lang_code.toLowerCase() == value.toLowerCase() ) {
4055 html += ' selected';
4056 }
4057 html += '>' + fv_player_editor_conf.field_languages[ lang_code ] + ' (' + lang_code + ')</option>'
4058 return html;
4059 }).join( '' ) +
4060 '</select>'
4061 );
4062 } );
4063 } );
4064 }
4065
4066 /**
4067 * Recalculate the data-index values for playlist items
4068 */
4069 function playlist_index() {
4070 $doc.trigger('fv-player-editor-initial-indexing');
4071
4072 $('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').each( index );
4073
4074 $('.fv-player-tab-video-files [data-playlist-item]').each( index );
4075
4076 $('.fv-player-tab.fv-player-tab-subtitles [data-playlist-item]').each( index );
4077
4078 function index() {
4079 /**
4080 * The element already had the data-index defined before, so it seems
4081 * we need to also set it with data(), since jQuery caches the first
4082 * .data() retrieval in the internal data object.
4083 *
4084 * We cannot just use .data() as we use CSS selectors for elements in JS.
4085 */
4086 $(this).attr('data-index', $(this).index() ).data( 'index', $(this).index() );
4087 }
4088
4089 }
4090
4091 // fills playlist editor table from individual video items
4092 function playlist_refresh() {
4093
4094 // fills playlist editor table from individual video items
4095 let video_files = jQuery('.fv-player-tab-video-files [data-playlist-item]');
4096 video_files.each( function( k, v ) {
4097 let current = jQuery(v),
4098 video = get_playlist_video_object( k ),
4099 currentUrl = get_field("src",current).val();
4100
4101 if (!currentUrl.length) {
4102 currentUrl = 'Video ' + (k + 1);
4103 }
4104
4105 let playlist_row = jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').eq( current.data('index') );
4106
4107 let video_preview = video.splash_display ? video.splash_display : get_field("splash", current).val(),
4108 playlist_img = jQuery('<img />')
4109 .attr('width', 120 )
4110 .attr('src', video_preview );
4111
4112 playlist_row.find('.fvp_item_video-thumbnail')
4113 .html( video_preview.length ? playlist_img : '' )
4114 .toggleClass( 'no-img', !video_preview.length );
4115
4116 let video_name = decodeURIComponent(currentUrl).split("/").pop();
4117 video_name = video_name.replace(/\+/g,' ');
4118 video_name = video_name.replace(/watch\?v=/,'YouTube: ');
4119
4120 let playlist_title = playlist_row.find('.fvp_item_video-filename');
4121 playlist_title.text( video_name );
4122
4123 // do not put in title if it's loading
4124 if (!playlist_title.hasClass('fv-player-shortcode-editor-small-spinner')) {
4125 let title = get_field("title",current).val();
4126 if( title ) {
4127 playlist_title.text( title );
4128 }
4129 }
4130
4131 playlist_row.find('.fvp_item_video-duration').text( seconds_to_hms( video.duration ) );
4132 });
4133 }
4134
4135 /**
4136 * Displays playlist editor
4137 * keywords: show playlist
4138 */
4139 function playlist_show() {
4140 current_video_db_id = 0;
4141
4142 editing_video_details = false;
4143
4144 $el_editor
4145 .addClass( 'is-playlist-active' )
4146 .removeClass( 'is-playlist' )
4147 .removeClass( 'is-singular' )
4148 .removeClass( 'is-singular-active' );
4149
4150 // show all the tabs previously hidden
4151 jQuery('.fv-player-tabs-header .nav-tab').attr('style',false);
4152 jQuery('a[data-tab=fv-player-tab-playlist]').trigger('click');
4153
4154 set_current_video_to_edit( -1 );
4155
4156 playlist_index();
4157
4158 playlist_refresh();
4159
4160 playlist_index();
4161
4162 jQuery('.fv-player-tab-playlist').show();
4163
4164 tabs_refresh();
4165
4166 show_end_of_video_actions();
4167
4168 $doc.trigger('fv-player-editor-video-opened', [ current_video_to_edit ] );
4169
4170 return false;
4171 }
4172
4173 function reload_preview( video_index ) {
4174 if(
4175 video_index > -1 && ( !current_player_object.videos || !current_player_object.videos[video_index] ) ||
4176 video_index== -1 && !current_player_object.videos
4177 ) {
4178 reset_preview();
4179 return;
4180 }
4181
4182 el_spinner.show();
4183
4184 debug_log('Running fv_player_db_load Ajax for preview.');
4185
4186 let time_start = performance.now();
4187
4188 // load player data and reload preview of the full player
4189 // when we go back from editing a single video in a playlist
4190 fv_player_shortcode_editor_ajax = jQuery.post( ajaxurl + '?fv_player_db_load=' + current_player_db_id, {
4191 action : 'fv_player_db_load',
4192 nonce : fv_player_editor_conf.db_load_nonce,
4193 playerID : current_player_db_id,
4194 current_video_to_edit: get_playlist_items_count() > 1 ? video_index : -1,
4195 }, function(response) {
4196
4197 debug_log('Finished fv_player_db_load Ajax in ' + debug_time( time_start ) + '.',response);
4198
4199 if ( response.html ) {
4200 // auto-refresh preview
4201 el_preview_target.html( response.html )
4202 $doc.trigger('fvp-preview-complete');
4203 }
4204 }).fail(function( jqXHR, textStatus, errorThrown ) {
4205 if ( jqXHR.status == 404) {
4206 overlay_show('message', 'The requested player #' + current_player_db_id + ' could not be found.');
4207 } else {
4208 overlay_show('message', 'An unexpected error has occurred while loading player #' + current_player_db_id + ': <code>' + errorThrown + '</code><br /><br />Please try again.');
4209 }
4210 }).always(function() {
4211 el_spinner.hide();
4212 });
4213 }
4214
4215 function remove_notices() {
4216 $el_notices.html('');
4217 }
4218
4219 function reset_editor_ids() {
4220 debug_log( 'reset_editor_ids' );
4221
4222 current_player_db_id = 0;
4223 current_player_object = false;
4224 current_video_db_id = 0;
4225
4226 current_post_id = 0;
4227 current_post_meta_key = 0;
4228 }
4229
4230 function reset_preview() {
4231 $el_preview.attr('class','preview-no');
4232
4233 if( fv_player_editor_conf.frontend ) {
4234 $el_editor.addClass('is-intro');
4235 }
4236 }
4237
4238 function seconds_to_hms( seconds ) {
4239 try {
4240 var duration_hms = new Date(seconds * 1000).toISOString().substr(11, 8);
4241 return duration_hms.replace( /^00:/, '' );
4242 } catch(e) {
4243 return '';
4244 }
4245 }
4246
4247 function set_current_video_to_edit( index ) {
4248 current_video_to_edit = parseInt( index );
4249 }
4250
4251 function set_control_fields( ajax_data ) {
4252 // add current video that we're editing into the save data
4253 ajax_data['current_video_to_edit'] = get_playlist_items_count() > 1 ? current_video_to_edit : -1;
4254
4255 // set current editor tab for ajax_data
4256 ajax_data['current_editor_tab'] = current_editor_tab;
4257
4258 // Used for Video Custom Fields
4259 if( current_post_id ) {
4260 ajax_data['current_post_id'] = current_post_id;
4261 }
4262 if( current_post_meta_key ) {
4263 ajax_data['current_post_meta_key'] = current_post_meta_key;
4264 }
4265
4266 // Used in FV Player Pay Per View to store the post ID for the product
4267 ajax_data['editor_post_id'] = fv_flowplayer_set_post_thumbnail_id;
4268
4269 return ajax_data;
4270 }
4271
4272 // used several times below, so it's in a function
4273 function set_editor_field( key, real_val, id, video_tab ) {
4274 var real_key = map_names_to_editor_fields(key);
4275
4276 if ( video_tab ) {
4277 $element = jQuery( '[name="' + real_key + '"]', video_tab );
4278 } else {
4279 $element = jQuery( '[name="' + real_key + '"]' );
4280 }
4281
4282 // special processing for end video actions
4283 if (real_key == 'fv_wp_flowplayer_field_end_action_value') {
4284 show_end_actions( false, real_val );
4285 return;
4286 } else if (['fv_wp_flowplayer_field_email_list', 'fv_wp_flowplayer_field_popup_id', 'fv_wp_flowplayer_field_redirect'].indexOf(real_key) > -1) {
4287 // ignore the original fields, if we still use old DB values
4288 return;
4289 }
4290
4291 // player and video IDs wouldn't have corresponding fields
4292 if ( $element && $element.length ) {
4293 // dropdowns could have capitalized values
4294 if ($element.get(0).nodeName == 'SELECT') {
4295 if ($element.find('option[value="' + real_val + '"]').length) {
4296 $element.val(real_val);
4297 } else {
4298 // try capitalized
4299 var caps = real_val.charAt(0).toUpperCase() + real_val.slice(1);
4300 $element.find('option').each(function() {
4301 if (this.text == caps) {
4302 $element.val(caps);
4303 }
4304 });
4305 }
4306 } else if ($element.get(0).nodeName == 'INPUT' && $element.get(0).type.toLowerCase() == 'checkbox') {
4307 let checked = real_val === '1' || real_val === 'on' || real_val === 'true' || real_val === 'yes';
4308
4309 // Is the value empty and is there default?
4310 if( real_val == '' && typeof(window.fv_player_editor_defaults[key]) != "undefined" ) {
4311 return;
4312 }
4313 $element.prop( 'checked', checked ).trigger('change');
4314
4315 } else {
4316 $element.val(real_val);
4317 }
4318
4319 // if an ID exists, this is a meta field
4320 // and the data id needs to be added to it as well
4321 if (typeof(id) != 'undefined') {
4322 $element.attr('data-id', id);
4323 }
4324 }
4325 }
4326
4327 function set_post_editor_content( html ) {
4328 if ( editor_button_clicked.className.indexOf('fv-player-editor-button') > -1 || is_fv_player_screen(editor_button_clicked) || is_fv_player_widgets(editor_button_clicked) || $(editor_button_clicked).parents('.fv-player-gutenberg').find('.fv-player-editor-field').length) {
4329 return;
4330 }
4331
4332 if ( instance_code_mirror ) {
4333 // set code mirror content
4334 instance_code_mirror.getDoc().setValue( html );
4335 } else if( typeof(FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length ){
4336 jQuery('#content:not([aria-hidden=true])').val(html);
4337
4338 } else if ( typeof(instance_fp_wysiwyg) != 'undefined' && ( instance_tinymce == undefined || typeof tinyMCE !== 'undefined' && tinyMCE.activeEditor.isHidden() ) ) {
4339 instance_fp_wysiwyg.SetHTML( html );
4340 }
4341 else if ( instance_tinymce ) { // instance_tinymce will be null if we're updating custom meta box
4342 instance_tinymce.setContent( html );
4343 }
4344 }
4345
4346 /**
4347 * Hide any overlays
4348 */
4349 function overlay_hide() {
4350 $('.fv-player-editor-overlay').hide();
4351 return false;
4352 }
4353
4354 /**
4355 * Show a certain kind of overlay
4356 */
4357 function overlay_show( type, message ) {
4358 overlay_hide();
4359 var overlayDiv = $('#fv-player-editor-'+type+'-overlay');
4360 overlayDiv.show();
4361
4362 if( typeof(message) != 'undefined' ) {
4363 overlayDiv.find('p').html( message );
4364 }
4365 return overlayDiv;
4366 }
4367
4368 /**
4369 * Populate content of the Embeds tab and show it if there is any content to be set
4370 *
4371 * @param string html The OL > LI list of posts which contain the same player.
4372 */
4373 function set_embeds( html ) {
4374 // ugly way of making sure that tab stays hidden as otherwise playlist_show() would reveal it
4375 $('[data-tab=fv-player-tab-embeds]').toggleClass('always-hide',!html);
4376 get_tabs('embeds').find('td').html(html);
4377 }
4378
4379 function shortcode_arg_split(sInput) {
4380 sInput[1] = sInput[1].replace(/\\;/gi, '<!--FV Flowplayer Caption Separator-->').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
4381 var aInput = sInput[1].split(';');
4382 for( var i in aInput ){
4383 aInput[i] = aInput[i].replace(/\\"/gi, '"');
4384 aInput[i] = aInput[i].replace(/\\<!--FV Flowplayer Caption Separator-->/gi, ';');
4385 aInput[i] = aInput[i].replace(/<!--FV Flowplayer Caption Separator-->/gi, ';');
4386 }
4387 return aInput;
4388 }
4389
4390 /*
4391 Also used by FV Player Pro and FV Player Pay Per View
4392 */
4393 function shortcode_parse_arg( sShortcode, sArg, bHTML, sCallback ) {
4394 var rDoubleQ = new RegExp(sArg+"=\"","g");
4395 var rSingleQ = new RegExp(sArg+"='","g");
4396 var rNoQ = new RegExp(sArg+"=[^\"']","g");
4397
4398 var rMatch = false;
4399 if( sShortcode.match(rDoubleQ) ) {
4400 //rMatch = new RegExp(sArg+'="(.*?[^\\\\/])"',"g");
4401 rMatch = new RegExp('[ "\']' + sArg + '="(.*?[^\\\\])"', "g");
4402 } else if (sShortcode.match(rSingleQ)) {
4403 rMatch = new RegExp('[ "\']' + sArg + "='([^']*?)'", "g");
4404 } else if (sShortcode.match(rNoQ)) {
4405 rMatch = new RegExp('[ "\']' + sArg + "=([^\\]\\s,]+)", "g");
4406 }
4407
4408 if( !rMatch ){
4409 return false;
4410 }
4411
4412 var aOutput = rMatch.exec(sShortcode);
4413 fv_player_editor.set_shortcode_remains( fv_player_editor.get_shortcode_remains().replace( rMatch, '' ) );
4414
4415 if( aOutput && bHTML ) {
4416 aOutput[1] = aOutput[1].replace(/\\"/g, '"').replace(/\\(\[|])/g, '$1');
4417 }
4418
4419 if( aOutput && sCallback ) {
4420 sCallback(aOutput);
4421 } else {
4422 return aOutput;
4423 }
4424 }
4425
4426 function shortcode_share_parse_arg( args ) {
4427 var field = fv_player_editor.get_field("share")[0];
4428 if (args[1] == 'yes' ) {
4429 field.selectedIndex = 1;
4430 } else if (args[1] == 'no' ) {
4431 field.selectedIndex = 2;
4432 } else {
4433 field.selectedIndex = 3;
4434 args = args[1].split(';');
4435 if( typeof(args[0]) == "string" ) fv_player_editor.get_field('share_url').val(args[0]);
4436 if( typeof(args[1]) == "string" ) fv_player_editor.get_field('share_title').val(args[1]);
4437 fv_player_editor.get_field("share_custom").show();
4438 }
4439 }
4440
4441 function shortcode_subtitle_parse_arg( args ) {
4442 var input = ('shortcode_subtitle_parse_arg',args);
4443 var aLang = input[0].match(/subtitles_([a-z][a-z])/);
4444 subtitle_language_add( input[1], aLang[1] );
4445 }
4446
4447 function subtitle_language_add( sInput, sLang ) {
4448 language_add( 'subtitles', sInput, sLang );
4449 }
4450
4451 /*
4452 * Adds another language for a field
4453 *
4454 * @param {string} field Field name
4455 * @param {string} sInput Value to input
4456 * @param {string} sLang Language to use
4457 * @param {jQuery} subtitles_tab Video Subtitles tab element
4458 * @param {int} video_meta_id Field video meta ID
4459 */
4460 function language_add( field, sInput, sLang, subtitles_tab, video_meta_id ) {
4461
4462 // Used when parsing a legacy shortcode like [fvplayer src="video-1.mp4" subtitles="video-1.vtt"]
4463 if( typeof( subtitles_tab ) == "undefined" ){
4464 // Reguired when parsing a legacy shortcode
4465 if( current_video_to_edit == -1 ) {
4466 current_video_to_edit = 0;
4467 }
4468
4469 subtitles_tab = get_tab( current_video_to_edit, 'subtitles' );;
4470 }
4471
4472 var subElement = false;
4473
4474 // If we are loading data, do we have an empty subtitle field?
4475 if( sInput ) {
4476 // TODO: Function to get last of the language inputs which is not a child
4477 subElement = $('.fv-player-editor-field-wrap-' + field + ' > .components-base-control > .components-base-control__field:last', subtitles_tab);
4478 if( subElement.length ) {
4479 if( get_field( field,subElement).val() ) {
4480 subElement = false;
4481 }
4482 }
4483 }
4484
4485 // If we do not have an empty subtitle field, add new
4486 if( !subElement ) {
4487
4488 // Get the last inputs and clear the values
4489 // TODO: Function to get last of the language inputs which is not a child
4490 subElement = $( $('.fv-player-editor-field-wrap-' + field + ' > .components-base-control > .components-base-control__field:last', subtitles_tab ).prop('outerHTML') );
4491 subElement.find('[name]').val('');
4492 subElement.removeAttr('data-id_videometa');
4493
4494 // Insert the new input after the last exiting input
4495 // TODO: Function to get last of the language inputs which is not a child
4496 subElement.insertAfter( $('.fv-player-editor-field-wrap-' + field + ' > .components-base-control > .components-base-control__field:last', subtitles_tab) );
4497
4498 if( !sInput ) {
4499 // force user to pick the language by removing the blank value and selecting what's first
4500 subElement.find('select option[value=""]').remove();
4501 setTimeout( function() {
4502 subElement.find('select option').eq(0).prop('selected',true);
4503 }, 0 );
4504 }
4505 }
4506
4507 if (typeof(video_meta_id) !== 'undefined') {
4508 subElement.attr('data-id_videometa', video_meta_id);
4509 }
4510
4511 let new_field = get_field( field, subElement);
4512 if( sInput ) {
4513 new_field.val(sInput).trigger('change');
4514 }
4515
4516 show_short_link( new_field );
4517
4518 if ( sLang ) {
4519 if( sLang == 'iw' ) sLang = 'he';
4520 if( sLang == 'in' ) sLang = 'id';
4521 if( sLang == 'jw' ) sLang = 'jv';
4522 if( sLang == 'mo' ) sLang = 'ro';
4523 if( sLang == 'sh' ) sLang = 'sr';
4524
4525 get_field( field + '_lang',subElement).val(sLang).trigger('change');
4526 }
4527 }
4528
4529 function tabs_refresh() {
4530 var visibleTabs = 0;
4531 $el_editor.find('a[data-tab]').removeClass('fv_player_interface_hide');
4532 $el_editor.find('.fv-player-tabs > .fv-player-tab').each(function(){
4533 var bHideTab = true
4534 $(this).find('.fv_player_editor-panel__body, .fv-player-editor-playlist-item').each(function(){
4535 if( $(this).css('display') === 'block' || $(this).css('display') === 'flex' || $(this).css('display') === 'table' ){
4536 bHideTab = false;
4537 return false;
4538 }
4539 });
4540 var tab;
4541 var data = jQuery(this).attr('class').match(/fv-player-tab-[^ ]*/);
4542 if(data[0]){
4543 tab = $el_editor.find('a[data-tab=' + data[0] + ']');
4544 }
4545
4546 if( fv_player_editor_conf.tabs && fv_player_editor_conf.tabs == 'none' ) {
4547 bHideTab = true;
4548 }
4549
4550 if(bHideTab){
4551 tab.addClass('fv_player_interface_hide')
4552 } else {
4553 tab.removeClass('fv_player_interface_hide');
4554 if(tab.css('display')!=='none')
4555 visibleTabs++
4556
4557 }
4558 });
4559
4560 if(visibleTabs<=1){
4561 $el_editor.find('.nav-tab').addClass('fv_player_interface_hide');
4562 $el_editor.find('.nav-tab-wrapper').addClass('fv_player_interface_hide');
4563 }
4564
4565 var end_actions_label = $('label[for=fv_wp_flowplayer_field_end_actions]');
4566 if( $el_editor.hasClass('is-playlist-active')){
4567 end_actions_label.html( end_actions_label.data('playlist-label') )
4568 } else {
4569 end_actions_label.html( end_actions_label.data('single-label') )
4570 }
4571
4572 }
4573
4574 /*
4575 Click handlers
4576 */
4577
4578 /*
4579 Click on Add Another Language (of Subtitles)
4580 */
4581 $doc.on('click', '.add_language', function() {
4582 language_add( $( this ).data( 'field_name' ), false );
4583 return false;
4584 });
4585
4586 /*
4587 Click on "Remove" to remove a language for a field
4588 */
4589 $doc.on('click', '.remove_language', function() {
4590
4591 let button = $( this ),
4592 field_name = button.data( 'field_name' ),
4593 field_label = button.data( 'field_label' ),
4594 field = jQuery(this).closest( '.components-base-control__field' ),
4595 id = field.attr('data-id_videometa')
4596
4597 if( !confirm('Would you like to remove this ' + field_label + '?') ) {
4598 return false;
4599 }
4600
4601 if (id) {
4602 deleted_video_meta.push(id);
4603 }
4604
4605 // TODO: How to get the field wrap and how to get its langauge versions?
4606 let language_fields = get_field( field_name, true )
4607 .closest('.components-base-control')
4608 .find( '.components-base-control__field' );
4609
4610 // if it's not the last subtitle, remove it completely
4611 if( language_fields.length > 1){
4612 field.remove();
4613
4614 // otherwise just empty the inputs to let user add new subtitles
4615 } else {
4616 field.find('[name]').val('');
4617 field.removeAttr('data-id_videometa');
4618 }
4619
4620 $doc.trigger('fv_player_editor_language_delete');
4621
4622 return false;
4623 });
4624
4625 /*
4626 Click on Import player
4627 */
4628 $doc.on('click', '#fv-player-editor-import-overlay-import', function() {
4629 var button = this,
4630 data = jQuery('#fv_player_import_data').val();
4631
4632 if (!data) {
4633 fv_player_editor.overlay_notice( button, 'No data to import!', 'warning', 5000 );
4634 return false;
4635 }
4636
4637 try {
4638 JSON.parse(data);
4639 } catch(e) {
4640 fv_player_editor.overlay_notice( button, 'Bad JSON format!', 'error', 5000 );
4641 return false;
4642 }
4643
4644 fv_player_editor.overlay_notice_close_all();
4645
4646 overlay_show('loading');
4647
4648 debug_log('Running fv_player_db_import Ajax.');
4649
4650 jQuery.post(ajaxurl, {
4651 action: 'fv_player_db_import',
4652 nonce: fv_player_editor_conf.db_import_nonce,
4653 data: data,
4654 cookie: encodeURIComponent(document.cookie),
4655 }, function(response) {
4656 if (response != '0' && !isNaN(parseFloat(response)) && isFinite(response)) {
4657 var playerID = response;
4658
4659 // add the inserted player's row
4660 jQuery.get(
4661 fv_player_editor_conf.admin_url + '&id=' + playerID,
4662 function (response) {
4663 jQuery('#the-list tr:first').before(jQuery(response).find('#the-list tr:first'));
4664 jQuery('.fv-wordpress-flowplayer-button').fv_player_box.close();
4665 }).fail(function() {
4666 jQuery('.fv-wordpress-flowplayer-button').fv_player_box.close();
4667 });
4668
4669 } else {
4670 fv_player_editor.overlay_notice( button, response, 'error' );
4671
4672 }
4673 }).fail(function() {
4674 fv_player_editor.overlay_notice( button, 'Unknown error!', 'error' );
4675 });
4676
4677 return false;
4678 });
4679
4680 /*
4681 Editor short links
4682 */
4683 var shortened_field_edited = false, // store field that if being edited
4684 shortened_field_button = false; // store Media Library button for the field
4685
4686 // click on shortened preveew, show original input and hide preview
4687 $doc.on('click', '.fv_player_editor_url_shortened', function() {
4688 var preview = jQuery(this),
4689 wrap = preview.closest('.components-base-control__field');
4690
4691 shortened_field_edited = preview.next('.fv_player_editor_url_field')
4692 shortened_field_button = wrap.find('.add_media')
4693
4694 preview.addClass('fv_player_interface_hide');
4695 preview.attr('title', '');
4696 shortened_field_button.show();
4697 shortened_field_edited.removeClass('fv_player_interface_hide');
4698 shortened_field_edited.focus();
4699 });
4700
4701 // Clicking outside of the edited field should close the field editing
4702 $el_editor.on( 'click', function(e) {
4703 if( shortened_field_edited ) {
4704 if(
4705 !$(e.target).hasClass('fv_player_editor_url_shortened') &&
4706 !$(e.target).parent().hasClass('fv_player_editor_url_shortened') &&
4707 !shortened_field_edited.is(e.target) &&
4708 !shortened_field_button.is(e.target)
4709 ) {
4710 show_short_link( shortened_field_edited );
4711 }
4712 }
4713 });
4714
4715 // focus lost from input
4716 $doc.on('change', '.fv_player_editor_url_field', function() {
4717 show_short_link(jQuery(this));
4718 });
4719
4720 $doc.on('fv_player_editor_finished fv_flowplayer_shortcode_item_switch', function() {
4721 jQuery('.fv_player_editor_url_field').each(function(index, item) {
4722 show_short_link(jQuery(item))
4723 });
4724 });
4725
4726 function show_short_link(field) {
4727 var value = field.val().trim(),
4728 preview = field.prev('.fv_player_editor_url_shortened'),
4729 button = field.closest( '.components-base-control__field').find('.add_media'); // Media Library Button
4730
4731 shortened_field_edited = false;
4732 shortened_field_button = false;
4733
4734 if( !value ) { // no value, hide preview
4735 field.removeClass('fv_player_interface_hide');
4736 preview.addClass('fv_player_interface_hide');
4737 button.show();
4738
4739 } else {
4740 field.addClass('fv_player_interface_hide');
4741 preview.removeClass('fv_player_interface_hide');
4742 preview.attr('title', value );
4743 preview.find('.link-preview').html( shorten_original_link( value ) ); // shorten preview link
4744 button.hide();
4745 }
4746 }
4747
4748 function shorten_original_link(original_link) {
4749 if(!original_link) return original_link;
4750
4751 let parts = original_link.trim().split('/'),
4752 is_hls = parts[parts.length - 1].indexOf('.m3u8') != -1,
4753 new_parts = [];
4754
4755 $(parts).each( function(k,v) {
4756 let c = '';
4757 // filename
4758 if( k == parts.length - 1 ) {
4759 // if it's HLS hilight the extension only
4760 if( is_hls ) {
4761 let filename_parts = v.split('.');
4762 if( filename_parts.length == 2 ) {
4763 new_parts.push( '<span class="path">'+filename_parts[0]+'.</span><span>'+filename_parts[1]+'</span>' );
4764 return true;
4765 }
4766 }
4767
4768 // HLS filename - use the directory name
4769 } else if( is_hls && k == parts.length - 2 ) {
4770
4771 // domain
4772 } else if ( k == 2 ) {
4773 // hilight bucket name for AWS
4774 let domain_parts = v.split('.');
4775 if( domain_parts.length > 1 && domain_parts[1].indexOf('s3') == 0 ) {
4776 let bucket_name = domain_parts[0];
4777 delete(domain_parts[0])
4778 new_parts.push( '<span>'+bucket_name+'</span><span class="path">'+domain_parts.join('.')+'</span>' );
4779 return true;
4780 }
4781 } else {
4782 c = ' class="path"';
4783 }
4784
4785 new_parts.push( '<span'+c+'>'+v+'</span>' );
4786 });
4787
4788 return new_parts.join('<span class="sep">/</span>');
4789 }
4790
4791 /*
4792 End of video action
4793 */
4794 $doc.on('change', '#fv_wp_flowplayer_field_end_actions', function() {
4795 var selected = this.value;
4796
4797 jQuery('.fv-player-editor-field-wrap-redirect').addClass('fv_player_interface_hide');
4798 jQuery('.fv-player-editor-field-wrap-popup_id').addClass('fv_player_interface_hide');
4799 jQuery('.fv-player-editor-field-wrap-email_list').addClass('fv_player_interface_hide');
4800
4801 switch (selected) {
4802 case 'redirect':
4803 jQuery('.fv-player-editor-field-wrap-redirect').removeClass('fv_player_interface_hide');
4804 break;
4805
4806 case 'popup':
4807 jQuery('.fv-player-editor-field-wrap-popup_id').removeClass('fv_player_interface_hide');
4808 break;
4809
4810 case 'email_list':
4811 jQuery('.fv-player-editor-field-wrap-email_list').removeClass('fv_player_interface_hide');
4812 break;
4813
4814 default:
4815 break;
4816 }
4817
4818 });
4819
4820 /*
4821 Extra fields to reveal when using a HLS or MPD stream
4822 */
4823 $doc.on('fv_flowplayer_shortcode_item_switch', function(e, index) {
4824 show_video_details(index);
4825 show_stream_fields_worker(index);
4826 show_rtmp_fields();
4827 show_end_of_video_actions();
4828 show_playlist_not_supported(index);
4829 });
4830
4831 $doc.on('fv_flowplayer_shortcode_new', function() {
4832 show_video_details(0);
4833 show_stream_fields_worker(0);
4834 show_rtmp_fields();
4835 show_end_of_video_actions();
4836 show_playlist_not_supported();
4837 });
4838
4839 function show_end_of_video_actions() {
4840 var player_object = get_current_player_object(),
4841 picked_action = player_object.end_actions,
4842 action_value = player_object.end_action_value;
4843
4844 select_end_of_video_action(picked_action, action_value);
4845 }
4846
4847 function select_end_of_video_action(action, value) {
4848 if( action == 'redirect' ) {
4849 jQuery('.fv-player-editor-field-wrap-redirect').removeClass('fv_player_interface_hide');
4850 get_field('redirect', false).val(value);
4851 } else if ( action == 'popup' ) {
4852 jQuery('.fv-player-editor-field-wrap-popup_id').removeClass('fv_player_interface_hide');
4853 get_field('popup', false).find('[value="'+value+'"]').prop('selected', true);
4854 } else if ( action == 'email_list' ) {
4855 jQuery('.fv-player-editor-field-wrap-email_list').removeClass('fv_player_interface_hide');
4856 get_field('email_list', false).find('[value="'+value+'"]').prop('selected', true);
4857 }
4858 }
4859
4860 function show_playlist_not_supported( index = 0 ) {
4861 let video = get_playlist_video_object( index ),
4862 result = {
4863 'supported': true
4864 }
4865
4866 if ( ! video.src ) {
4867 return;
4868 }
4869
4870 if (
4871 video.src.indexOf('//vimeo.com') > -1 ||
4872 video.src.indexOf('//vimeopro.com') > -1
4873 ) {
4874 result.supported = false;
4875 }
4876
4877 // fire up a JS event for the FV Player Pro to catch,
4878 // so it can check the URL and make sure we don't show
4879 // a warning message for PRO-supported video types
4880 $doc.trigger('fv-player-editor-src-change', [ video.src, result, this ]);
4881
4882 if( result.supported ) {
4883 playlist_buttons_disable(false);
4884
4885 } else {
4886 // Disable the playlist editing buttons if the video type is not supported in playlists
4887 playlist_buttons_disable('FV Player Pro required for playlists with this video type');
4888
4889 }
4890 }
4891
4892 function show_rtmp_fields() {
4893 var video_object = get_current_video_object(),
4894 is_enabled = video_object.rtmp || video_object.rtmp_path
4895
4896 get_field( 'rtmp_show', true )
4897 .prop('checked', is_enabled)
4898 .trigger('change');
4899
4900 if( is_enabled ) {
4901 get_field( 'toggle_advanced_settings', true )
4902 .prop('checked', true)
4903 .trigger('change');
4904 }
4905 }
4906
4907 function show_stream_fields_worker( index = 0 ) {
4908 var encrypted = get_current_player_object() ? get_playlist_video_meta_value( 'encrypted', index ) : false;
4909
4910 // hlskey
4911 var hlskey = get_field('hls_hlskey', true);
4912 if( encrypted || hlskey.val() ) {
4913 hlskey.closest('.fv_player_interface_hide').show();
4914 } else {
4915 hlskey.closest('.fv_player_interface_hide').hide();
4916 }
4917
4918 // get live from video object
4919 var live_field = get_field('live', true),
4920 live_value = get_current_video_object() ? get_current_video_object().live : false,
4921 error = get_playlist_video_meta_value( 'error', index ),
4922 // Show the stream settings if it's not found and it's HLS stream
4923 // as it often returns 404 error if it's not live at the moment
4924 stream_fields_visible = !! ( error && is_live_stream( get_current_video_object() ) );
4925
4926 live_field.prop('checked', !!live_value);
4927 live_field.closest('.fv_player_interface_hide').toggle( !!live_value || stream_fields_visible );
4928
4929 checkbox_toggle_worker( jQuery(live_field).parent('.components-form-toggle'), 'live', !!live_value );
4930
4931 jQuery.each( [ 'audio', 'dvr' ], function(k,v) {
4932
4933 var field = get_field(v, true),
4934 meta = get_current_player_object() ? get_playlist_video_meta_value( v, index ) : false,
4935 force_visible = stream_fields_visible,
4936 is_hls = get_current_video_object().src && get_current_video_object().src.match( /\.m3u8/ );
4937
4938 // Show Audio track checkbox if we have HLS with unknown dimensions
4939 if ( 'audio' === v && is_hls && get_current_video_object().width == 0 && get_current_video_object().height == 0 ) {
4940 force_visible = true;
4941 }
4942
4943 field.prop('checked', !!meta);
4944 field.closest('.fv_player_interface_hide').toggle( !!meta || force_visible );
4945
4946 checkbox_toggle_worker( jQuery(field).parent('.components-form-toggle'), v, !!meta );
4947 });
4948
4949 }
4950
4951 function show_video_details( index = 0 ) {
4952 var video_tab = get_tab(index,'video-files'),
4953 video = get_playlist_video_object( index ),
4954 error = get_playlist_video_meta_value( 'error', index )
4955
4956 var video_info = get_field('video_info', video_tab).find('ul'),
4957 show = false;
4958
4959 video_info.html('');
4960
4961 if( video.duration > 0 ) {
4962 video_info.append( '<li title="Duration">' + seconds_to_hms(video.duration) + '</li>' );
4963 show = true;
4964 }
4965 if( video.width > 0 && video.height > 0 ) {
4966 video_info.append( '<li title="Video dimension in pixels">' + video.width + 'x' + video.height + '</li>' );
4967 show = true;
4968 }
4969 if( video.aspect_ratio ) {
4970 video_info.append( '<li title="Aspect ratio">' + get_pretty_aspect_ratio(video) + '</li>' );
4971 show = true;
4972 }
4973 if( error ) {
4974 video_info.append( '<li class="error">' + error + '</li>' );
4975 show = true;
4976
4977 if ( is_live_stream( video ) ) {
4978 video_info.append( '<li>FV Player was not able to determine the stream type, please check what applies below.</li>' );
4979 video_info.append( '<li>This error is normal for some of the HLS live streams if they are not currently streaming.</li>' );
4980 }
4981 }
4982
4983 if( show ) {
4984 video_info.closest('.fv_player_interface_hide').removeClass('fv_player_interface_hide');
4985 }
4986
4987 }
4988
4989 function checkbox_toggle_worker( wrap, name, checked ) {
4990
4991 var compare = checked ? 1 : 0;
4992
4993 wrap.toggleClass( 'is-checked', checked );
4994
4995 // If the checkox is checked its parents must be visible
4996 if( checked ) {
4997 wrap
4998 .closest('.fv-player-editor-children-wrap')
4999 .removeClass('fv_player_interface_hide');
5000 }
5001
5002 wrap.toggleClass( 'is-default', !!window.fv_player_editor_defaults[name] );
5003
5004 if( window.fv_player_editor_dependencies[name] ) {
5005 jQuery.each( window.fv_player_editor_dependencies[name], function(value,inputs) {
5006
5007 jQuery.each( inputs, function(k,input_name) {
5008
5009 // We get the last matching field as if we are loading a playlist, we might have multiple fields
5010 // TODO: Do this more sensibly, like when switching playlist items
5011 let field_wrap = $('.fv-player-editor-field-wrap-' + input_name + ':last' );
5012
5013 // TODO: What should be saved when it's enabled?
5014 field_wrap.toggleClass( 'is-visible-dependency', value == compare );
5015 field_wrap.toggleClass( 'is-hidden-dependency', value != compare );
5016 });
5017 });
5018 }
5019
5020 wrap.closest( '.fv-player-editor-children-wrap' ).find( '.fv-player-editor-field-children-'+name ).toggle( checked );
5021 }
5022
5023 function text_and_select_worker( input, parent, name, wrap ) {
5024 // Reveal the input if it has value even if it's not enabled in Post Interface options
5025 if ( input.val() ) {
5026 // Only if it's not field with shortening
5027 if ( input.prev( '.fv_player_editor_url_shortened' ).length === 0 ) {
5028 input.closest( '.fv_player_interface_hide' ).show();
5029 }
5030 wrap.show();
5031 }
5032
5033 // Show children inputs if input has value
5034 parent.find('.fv-player-editor-field-children-' + name ).toggle( !! input.val() );
5035 }
5036
5037 function show_end_actions( e, value ) {
5038 // redirect, popup and email_list
5039 var type = jQuery('#fv_wp_flowplayer_field_end_actions').val();
5040
5041 jQuery('.fv_player_actions_end-toggle').hide().find('[name]').val('');
5042
5043 // The field id is different for popup
5044 if( type == 'popup' ) {
5045 type = 'popup_id';
5046 }
5047
5048 var field = jQuery('#fv_wp_flowplayer_field_' + type);
5049 field.parents('tr').show();
5050 if( value ) {
5051 field.val( value ).trigger( 'change' );
5052 }
5053 }
5054
5055 function init_saved_player_fields() {
5056 deleted_videos = [];
5057 deleted_video_meta = [];
5058 deleted_player_meta = [];
5059 }
5060
5061 function title_editor_close() {
5062 $('.fv-player-editor-playlist-item .fvp_item_video-title-wrap').show();
5063 $('.fv-player-editor-playlist-item .fvp_item_video-edit-input').hide();
5064 }
5065
5066 /*
5067 Mark each manually updated title or splash field as such
5068 */
5069 $doc.on('input change', '#fv_wp_flowplayer_field_splash, #fv_wp_flowplayer_field_title', function() {
5070
5071 if ( is_loading ) {
5072 return;
5073 }
5074
5075 let $input;
5076
5077 // if this element already has data set, don't do any of the selections below
5078 if (typeof( jQuery(this).data('fv_player_user_updated') ) != 'undefined') {
5079 return;
5080 }
5081
5082 if( this.id == 'fv_wp_flowplayer_field_splash' ) {
5083 $input = get_field('auto_splash', true);
5084 } else {
5085 $input = get_field('auto_caption', true);
5086 }
5087
5088 if( $input.length > 0 ) {
5089 $input.val(0);
5090
5091 debug_log(this.id+' has been updated manually!');
5092 }
5093
5094 });
5095
5096 // Public stuff
5097 return {
5098 add_notice,
5099
5100 playlist_index,
5101
5102 reload_preview( index ) {
5103 return reload_preview(index);
5104 },
5105
5106 fv_wp_flowplayer_dialog_resize,
5107
5108 get_current_player_db_id,
5109
5110 get_current_player_object,
5111
5112 get_current_video_meta,
5113
5114 get_current_video_meta_value,
5115
5116 get_current_video_object,
5117
5118 get_current_video_index() {
5119 return parseInt( current_video_to_edit );
5120 },
5121
5122 get_current_video_db_id() {
5123 return current_video_db_id;
5124 },
5125
5126 get_edit_lock_removal() {
5127 return edit_lock_removal;
5128 },
5129
5130 get_field( key, where ) {
5131 return get_field( key, where );
5132 },
5133
5134 get_playlist_video_meta,
5135
5136 get_playlist_video_meta_value,
5137
5138 get_playlist_video_object,
5139
5140 get_shortcode_remains: function() {
5141 return shortcode_remains;
5142 },
5143
5144 get_tab( index, tab ) {
5145 return get_tab( index, tab );
5146 },
5147
5148 insertUpdateOrDeletePlayerMeta,
5149
5150 insertUpdateOrDeleteVideoMeta,
5151
5152 is_busy_saving() {
5153 return ajax_save_this_please || is_saving;
5154 },
5155
5156 // Allows external entities to set which video will be edited once the editor opens
5157 set_current_video_to_edit,
5158
5159 set_edit_lock_removal( val ) {
5160 edit_lock_removal = val;
5161 },
5162
5163 set_shortcode_remains: function(value) {
5164 shortcode_remains = value;
5165 },
5166
5167 /**
5168 * Show a notice in the overlay above the editor
5169 *
5170 * @param {Object} button The button in the overlay that was clicked
5171 * Used to find the overlay and the notice in it
5172 * @param {string} html Content of the notice
5173 * @param {string} type success|error
5174 * @param {int} close_after Optional number of miliseconds to close the
5175 * notice after
5176 *
5177 */
5178 overlay_notice: function(button, html, type, close_after ) {
5179 var overlay = jQuery(button).closest('.fv-player-editor-overlay'),
5180 notice = overlay.find('.fv-player-editor-overlay-notice');
5181
5182 notice
5183 .html(html)
5184 .removeClass('notice-error')
5185 .removeClass('notice-success')
5186 .addClass('notice-'+type)
5187 .css('visibility', 'visible');
5188
5189 overlay.show();
5190
5191 if( close_after ) {
5192 setTimeout(function() {
5193 notice.css('visibility', 'hidden');
5194 }, close_after);
5195 }
5196 },
5197
5198 overlay_notice_close_all: function() {
5199 $('.fv-player-editor-overlay-notice').css('visibility', 'hidden');
5200 },
5201
5202 playlist_item_add: playlist_item_add,
5203
5204 playlist_show: playlist_show,
5205
5206 shortcode_parse_arg,
5207
5208 // We keep it for backwards compatibility
5209 editor_resize: function() {},
5210
5211 get_playlist_items_count,
5212
5213 insert_button_toggle,
5214
5215 copy_player_button_toggle,
5216
5217 // TODO: Deprecated
5218 meta_data_load_started() {
5219 is_loading_meta_data++;
5220 },
5221
5222 meta_data_load_finished() {
5223 is_loading_meta_data--;
5224 },
5225
5226 editor_open: editor_open,
5227
5228 error_add( identifier, txt ) {
5229 errors[ identifier ] = txt;
5230
5231 // no save button while we have errors
5232 insert_button_toggle_disabled( true );
5233 },
5234
5235 error_remove( identifier ) {
5236 delete errors[ identifier ];
5237
5238 if ( !has_errors() ) {
5239 // enable save button
5240 insert_button_toggle_disabled( false );
5241 }
5242 },
5243
5244 errors_remove( prefix ) {
5245 var errors_found = [];
5246
5247 for ( var key in errors ) {
5248 if ( key.startsWith( prefix ) ) {
5249 errors_found.push( key );
5250 }
5251 }
5252
5253 if ( errors_found.length ) {
5254 for ( var val of errors_found ) {
5255 delete errors[ val ];
5256 }
5257 }
5258
5259 if ( has_errors() ) {
5260 // enable save button
5261 insert_button_toggle_disabled( false );
5262 }
5263 },
5264
5265 has_errors,
5266
5267 b64EncodeUnicode(str) {
5268 return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
5269 return String.fromCharCode('0x' + p1);
5270 }));
5271 },
5272 };
5273
5274 })(jQuery);
5275
5276 if ( window.wp && wp.data ) {
5277 wp.data.subscribe( function() {
5278 if ( wp.data.select('core/editor') && wp.data.select('core/editor').getCurrentPost().fv_player_reload ) {
5279 setTimeout( function() {
5280 location.href = location.href;
5281 }, 0 );
5282 }
5283 } );
5284 }
5285 });
5286