/* eslint-disable no-global-assign */ /*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, */ /*global FCKeditorAPI, setPostThumbnailL10n, send_to_editor, tinymce*/ jQuery(function() { // The actual editor window.fv_player_editor = (function($) { var $doc = $(document), $el_editor = $('#fv-player-shortcode-editor'), $el_preview = $('#fv-player-shortcode-editor-preview'), el_spinner = $('#fv-player-shortcode-editor-preview-spinner'), el_preview_target = $('#fv-player-shortcode-editor-preview-target'), $el_notices = $('#fv-player-editor-notices'), // data to save in Ajax ajax_save_this_please = false, // last saved data to detect changes for auto-saving ajax_save_previous = false, current_player_db_id = 0, current_player_object = false, current_video_db_id = 0, current_video_to_edit = -1, current_editor_tab = false, // Used for Video Custom Fields current_post_id = 0, current_post_meta_key = 0, deleted_videos = [], deleted_video_meta = [], deleted_player_meta = [], prevent_reload_for_current_save = false, // stores the button which was clicked to open the editor editor_button_clicked = 0, // the post editor content being edited editor_content, // used in WP Heartbeat edit_lock_removal = {}, // CodeMirror instance, if any instance_code_mirror, // keep track of the last cursor position in CodeMirror instance_code_mirror_cursor_last, // TinyMCE instance, if any instance_tinymce, // Foliopress WYSIWYG instance, if any instance_fp_wysiwyg, is_loading = false, // are we editing player which is not yet in DB? is_unsaved = true, is_playlist_hero_editing = false, // is the player already saved in the DB but actually // still in a "draft" status? i.e. not published yet has_draft_status = true, // will be > 0 when any meta data are loading that need saving along with the form (example: S3 video duration, PPV product creation) // ... this prevents overlay closing until all meta required data are loaded and stored is_loading_meta_data = 0, // whether we're editing a single video (true) or showing a playlist view (false) editing_video_details = false, // are we currently saving data? is_saving = false, fv_player_shortcode_editor_ajax, // used when editing shortcode in TinyMCE helper_tag = window.fvwpflowplayer_helper_tag, // the part of shortcode outside of [fvplayer id="XYZ"] // also accessed from outside shortcode_remains, // Some shortcode args should be kept. For example if you edit // [fvplayer id="1" sort="newest"] that sort should not be removed store_shortcode_args = {}, // Some shortcode args do not have a DB counterpars, so they should always // be kept on the shortcode. For example if you edit // [fvplayer src="some_video_url" sort="newest"] that sort should not be removed, // as it's not been transferred into the DB always_keep_shortcode_args = {}, // 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. store_rtmp_server = '', // stores parts of editor HTML which are later re-used when adding new items template_playlist_item, template_video, template_subtitles_tab, // used to remember which widget we are editing, if any widget_id, is_editing_playlist_item_title = false, // list of errors that currently prevent auto-saving in the form of: { error_identifier_with_(plugin_)prefix : "the actual error text to show" } // ... this will be shown in place of the "Saved!" message bottom overlay and it will always show only the first error in this object, // 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. errors = {}, store_debug_log = []; debug_log('Loading...'); /** * Adds a notice at the bottom of player. Used for successful save and save errors. Notices are removed on successful save. * * @param {string} id * @param {string} msg * @param {int} [timeout] Duration for which it should appear or omit for persistent message */ function add_notice( id, msg, timeout ) { var notice = $('
'+msg+'
' ); $el_notices.append( notice ); if( typeof(timeout) == 'number' ) { setTimeout( function() { notice.fadeOut(); }, timeout ); } } function add_shortcode_args( args ) { let params = jQuery.map( args, function (value, index) { return index + '="' + value + '"'; }); return params.length ? ' ' + params.join(' ') : ''; } function copy_player_button_toggle( show ) { $('#fv-player-shortcode-editor .copy_player').toggle( show ); } function check_for_player_meta_field(fieldName) { return get_field_conf( fieldName ).store == 'player_meta'; } function check_for_video_meta_field(fieldName) { if ( get_field_conf( fieldName ).store == 'video_meta' ) { return true; } return [ 'fv_wp_flowplayer_field_duration', 'fv_wp_flowplayer_field_dvr', 'fv_wp_flowplayer_field_auto_splash', 'fv_wp_flowplayer_field_auto_caption', 'fv_wp_flowplayer_field_audio' ].indexOf(fieldName) > -1; } function get_current_player_db_id() { return current_player_db_id; } function get_current_player_object() { return current_player_object; } function get_current_video_meta( key ) { var video_object = get_current_video_object(), video_meta = false; if( video_object ) { $( video_object.meta ).each( function(k,v) { if( v.meta_key == key ) { video_meta = v; return false; } } ); } return video_meta; } function get_current_video_meta_value( key ) { var video_object = get_current_video_object(), video_id = video_object && video_object.id ? video_object.id : -1, video_meta = get_current_video_meta( key ); if( video_meta ) { debug_log( "Video meta '"+key+"' for #"+video_id, video_meta.meta_value ); return video_meta.meta_value; } debug_log( "Video meta '"+key+"' not found for #"+video_id ); return false; } function get_current_video_object() { if( current_video_to_edit == -1 ) return false; if( current_player_object.videos && current_player_object.videos[current_video_to_edit] ) { return current_player_object.videos[current_video_to_edit]; } return false; } /* * Used when entering link to the "hero" field. * If it's used in the playlist mode, we need to insert the hidden new playlist row and use the added field. */ function get_hero_src_field( input ) { let editor_src_field = get_field("src") if( $( '#playlist-hero:visible' ).length && input.data('playlist-hero') ) { // Remember the new item index to not add new video on each keyup event! if( !input.data( 'new-item-index' ) ) { input.data( 'new-item-index', get_playlist_items_count() ); } editor_src_field = editor_src_field.eq( input.data( 'new-item-index' ) ); // Add new playlist item if it's not there if( !editor_src_field.length ) { let new_item = playlist_item_add(); // If we are going to type in the URL, do not show the row if( input.attr( 'type') == 'text' ) { $( '#fv-player-editor-playlist .fv-player-editor-playlist-item:last').hide(); } editor_src_field = get_field( "src", new_item.video_tab ); } } return editor_src_field; } /* * Returns the number of videos in a playlist for the current player. */ function get_playlist_items_count() { // TODO: Could we just use current_player_object.videos.length ? Would that work with editing? return jQuery('#fv-player-editor-playlist .fv-player-editor-playlist-item').length; } function get_playlist_video_meta( meta_key, index ) { var video_object = current_player_object.videos && current_player_object.videos[index], video_meta = false; if( video_object ) { $( video_object.meta ).each( function(k,v) { if( v.meta_key == meta_key ) { video_meta = v; return false; } } ); } return video_meta; } function get_playlist_video_object( index ) { if( current_player_object.videos && current_player_object.videos[index] ) { return current_player_object.videos[index] } return false; } function get_playlist_video_meta_value( meta_key, index ) { var video_meta = get_playlist_video_meta( meta_key, index ); if( video_meta ) { return video_meta.meta_value; } return false; } /** * A shorthand to save you from all the "fv_wp_flowplayer_field_" * when selecting fields * * @param {string} key The field key. For example "src" gives * you "fv_wp_flowplayer_field_src" * @param {bool|jQuery|string} where Lets you narrow down the element wher you * want to locate he field. You can use a jQuery * element or a string selector for jQuery. * Or TRUE to get the currently open playlist item. * * @return {jQuery} The field element/elements (in case of multiple language subtitles) */ function get_field( key, where ) { var element = false, selector = '.' + map_names_to_editor_fields(key) + ', [name=' + map_names_to_editor_fields(key) + ']'; if( typeof(where) == 'boolean' && where ) { where = jQuery('.fv-player-tab [data-index='+current_video_to_edit+']'); element = where.find(selector); } else if( where && typeof(where) == "object" ) { element = where.find(selector); } else if( where && typeof(where) == "string" ) { element = $el_editor.find(where).find(selector); } else { element = $el_editor.find(selector); } if( !element.length ) { console.log('FV Player Editor Warning: field '+key+' not found'); } return element; } function get_field_conf( name ) { name = get_field_name( name ); if ( window.fv_player_editor_fields && window.fv_player_editor_fields[ name ] ) { return window.fv_player_editor_fields[ name ]; } return {}; } /** * Gives you "src" out of "fv_wp_flowplayer_field_src" * * @param {string} name The field name * * @return {string} The field.... real name? */ function get_field_name( name ) { if (name.indexOf('fv_wp_flowplayer_field_') > -1) { return name.replace('fv_wp_flowplayer_field_', ''); } return name; } /** * Gives you the desired tab with video information * * @param {int|string} index Number, or first, or last * @param {string} tab Tab name to obtain, options: * * video-files * * subtitles * * @return {object} The tab element */ function get_tab( index, tab ) { var selector = '.fv-player-tab-'+tab+' [data-playlist-item]'; if( index == 'first' ) { selector += ':first'; } else if( index == 'last' ) { selector += ':last'; } else { selector += '[data-index='+index+']'; } return $el_editor.find(selector); } /** * Gives you all desired tabs of a certain kind * * @return {object} The tab elements */ function get_tabs( tab ) { var selector = '.fv-player-tab-'+tab+' [data-playlist-item]'; return $el_editor.find(selector); } function has_errors() { for ( var i in errors ) { return errors[ i ]; } return false; } function hide_inputs() { if( fv_player_editor_conf.hide ) { $.each( fv_player_editor_conf.hide, function( k, v ) { try { if( 'configure-video' === v ) { $( 'a.configure-video' ).hide(); $( "" ).appendTo( "head" ) } else { $( '.fv-player-editor-field-wrap-'+v ).hide(); $( '[name=fv_wp_flowplayer_field_'+v+']' ).hide(); } } catch(e) { debug_log( 'Hide Field: Failure', e ); } } ); } } /** * Automatically handles new, updated or removed player meta data * via JS. * * @param options Object with the following elements: * element -> the actual element on page (input, select...) to check and get meta value from * data -> existing player data, including player meta data * meta_section -> section for the meta data, for example "player" for common metas, "ppv" for pay per view plugin etc. * meta_key -> rhe actual key to check for and potentially add/update/remove * handle_delete -> if true, value-less ('') elements will be considered indication that the meta key should be deleted * delete_callback -> if set, this function is called when a meta key is deleted * edit_callback -> if set, this function is called when a meta key is updated * insert_callback -> if set, this function is called when a meta key is added */ function insertUpdateOrDeletePlayerMeta(options) { var $element = jQuery(options.element), value = map_input_value( $element ); if ( ! options.meta_section ) { options.meta_section = 'player'; } // don't do anything if we've not found the actual element if (!$element.length) { return; } // check whether to update or delete this meta if ($element.data('id')) { // only delete this meta if delete was not prevented via options // and if there was no value specified, otherwise update if ((!options.handle_delete || options.handle_delete !== false) && !value) { deleted_player_meta.push( $element.data('id') ); $element .removeData('id') .removeAttr('data-id'); // execute delete callback, if present if (options.delete_callback && typeof(options.delete_callback) == 'function') { options.delete_callback(); } } else { if (typeof(options.data) != 'undefined' && typeof(options.data['player_meta'][options.meta_section]) == 'undefined') { options.data['player_meta'][options.meta_section] = {}; } // update if we have an ID if (typeof(options.data) != 'undefined') { options.data['player_meta'][options.meta_section][options.meta_key] = { 'id': $element.data('id'), 'value': value } } // execute update callback, if present if (options.edit_callback && typeof(options.edit_callback) == 'function') { options.edit_callback(); } } } else if (value || options.handle_delete !== false ) { if (typeof(options.data) != 'undefined' && typeof(options.data['player_meta'][options.meta_section]) == 'undefined') { options.data['player_meta'][options.meta_section] = {}; } // insert new data if no meta ID if (typeof(options.data) != 'undefined') { options.data['player_meta'][options.meta_section][options.meta_key] = { 'value': value } } if ( value ) { // execute insert callback, if present if (options.insert_callback && typeof(options.insert_callback) == 'function') { options.insert_callback(); } } else { if (options.delete_callback && typeof(options.delete_callback) == 'function') { options.delete_callback(); } } } } /** * Automatically handles new, updated or removed video meta data * via JS. * * @param options Object with the following elements: * element -> the actual element on page (input, select...) to check and get meta value from * data -> existing player data, including video meta data * meta_section -> section for the meta data, for example "player" for common metas, "ppv" for pay per view plugin etc. * meta_key -> rhe actual key to check for and potentially add/update/remove * handle_delete -> if true, value-less ('') elements will be considered indication that the meta key should be deleted * delete_callback -> if set, this function is called when a meta key is deleted * edit_callback -> if set, this function is called when a meta key is updated * insert_callback -> if set, this function is called when a meta key is added */ function insertUpdateOrDeleteVideoMeta(options) { var $element = jQuery(options.element), value = map_input_value( $element ); // don't do anything if we've not found the actual element if (!$element.length) { return; } // check whether to update or delete this meta if ($element.data('id')) { // only delete this meta if delete was not prevented via options // and if there was no value specified, otherwise update if ((!options.handle_delete || options.handle_delete !== false) && !$element.val()) { deleted_video_meta.push( $element.data('id') ); $element .removeData('id') .removeAttr('data-id'); // execute delete callback, if present if (options.delete_callback && typeof(options.delete_callback) == 'function') { options.delete_callback(); } } else { if (typeof(options.data) != 'undefined' && typeof(options.data['video_meta'][options.meta_section]) == 'undefined') { options.data['video_meta'][options.meta_section] = {}; } if (typeof(options.data) != 'undefined') { // update if we have an ID options.data['video_meta'][options.meta_section][options.meta_index][options.meta_key] = { 'id': $element.data('id'), 'value': value } // execute update callback, if present if (options.edit_callback && typeof(options.edit_callback) == 'function') { options.edit_callback(); } } } } else if (value) { if (typeof(options.data) != 'undefined' && typeof(options.data['video_meta'][options.meta_section]) == 'undefined') { options.data['video_meta'][options.meta_section] = {}; } // insert new data if no meta ID if (typeof(options.data) != 'undefined') { options.data['video_meta'][options.meta_section][options.meta_index][options.meta_key] = { 'value': value } // execute insert callback, if present if (options.insert_callback && typeof(options.insert_callback) == 'function') { options.insert_callback(); } } } } $doc.ready( function(){ var next = false, // track if the player data has changed while saving overlay_close_waiting_for_save = false, ajax_saving = true, int_keyup = false; /*$(window).on('beforeunload', function(e) { if (is_draft && is_draft_changed) { return e.originalEvent.returnValue = 'You have unsaved changes. Are you sure you want to close this dialog and loose them?'; } });*/ if( jQuery().fv_player_box ) { debug_log('Attaching click actions...'); $doc.on( 'click', '.fv-wordpress-flowplayer-button, .fv-player-editor-button, .fv-player-edit', function(e) { // make the TinyMCE editor below this button active, // as otherwise we would be inserting into the last TinyMCE instance // on the page if no TinyMCE instance was clicked into - which is not what we want to do if (typeof(tinyMCE) != 'undefined' && typeof(tinyMCE.activeEditor) != 'undefined' && this.className.indexOf('fv-wordpress-flowplayer-button') > -1) { var $btn = jQuery(this), $wraper_div = $btn.parents('.wp-editor-wrap:first'); for (var i in tinyMCE.editors) { if (tinyMCE.editors[i].editorContainer && tinyMCE.editors[i].editorContainer.id && $wraper_div.find('#' + tinyMCE.editors[i].editorContainer.id).length) { tinyMCE.activeEditor = tinyMCE.editors[i]; break; } } } editor_button_clicked = this; e.preventDefault(); $.fv_player_box( { onComplete : editor_open, onClosed : editor_close, onOpen: lightbox_open } ); widget_id = $(this).data().number; }); /** * Elementor Widget support */ if ( window.elementor && window.elementor.channels && window.elementor.channels.editor ) { // "Configure Player" button elementor.channels.editor.on( 'fv-player-elementor-editor-open', function(e) { editor_button_clicked = e.el; $.fv_player_box( { onComplete : editor_open, onClosed : editor_close, onOpen: lightbox_open } ); }); // "Select Media" button for "Source URL" elementor.channels.editor.on( 'fv-player-elementor-pick-source_url ', function(e) { fv_flowplayer_uploader_button = jQuery( e.el ); $( '.elementor-control-source_url [data-setting="url"]' ).addClass( 'fv_flowplayer_target' ); fv_flowplayer_uploader_open(); } ); // "Select Media" button for "Splash URL" elementor.channels.editor.on( 'fv-player-elementor-pick-splash_url ', function(e) { fv_flowplayer_uploader_button = jQuery( e.el ); $( '.elementor-control-splash_url [data-setting="url"]' ).addClass( 'fv_flowplayer_target' ); fv_flowplayer_uploader_open(); } ); } /** * Look for buttons in the block editor canvas iframe (post editor and Site Editor). */ function getEditorCanvasIframe() { return jQuery('iframe[name="editor-canvas"], iframe.editor-canvas__iframe, iframe.edit-site-visual-editor__editor-canvas'); } function setupSiteEditorHandlers() { var site_editor_iframe = getEditorCanvasIframe().contents(); if( site_editor_iframe.length ) { debug_log( 'Editor canvas iframe found!' ); // FV Player Editor site_editor_iframe.off( 'click', '.fv-wordpress-flowplayer-button, .fv-player-editor-button, .fv-player-edit' ); site_editor_iframe.on( 'click', '.fv-wordpress-flowplayer-button, .fv-player-editor-button, .fv-player-edit', function(e) { editor_button_clicked = this; e.preventDefault(); $.fv_player_box( { top: "100px", initialWidth: 1100, initialHeight: 50, width:"1100px", height:"100px", href: "#fv-player-shortcode-editor", inline: true, title: 'Add FV Player', onComplete : editor_open, onClosed : editor_close, onOpen: lightbox_open } ); widget_id = $(this).data().number; }); // FV Player Block "Select Media" button site_editor_iframe.off( 'click', '.fv-player-gutenberg-media' ); site_editor_iframe.on( 'click', '.fv-player-gutenberg-media', fv_flowplayer_uploader_init ); } } // Initial setup with interval var site_editor_load = setInterval( function() { var canvas = getEditorCanvasIframe(); if( canvas.length ) { clearInterval(site_editor_load); setupSiteEditorHandlers(); // Watch for iframe load events (fires when iframe reloads) canvas.on('load', function() { debug_log( 'Editor canvas iframe loaded, reattaching handlers' ); setTimeout( setupSiteEditorHandlers, 100 ); }); } }, 1000 ); $doc.on( 'click', '.fv-player-export', function(e) { let $element = jQuery(this), player_id = $element.data('player_id'); e.preventDefault(); $.fv_player_box( { onComplete : function() { overlay_show('loading'); debug_log('Running fv_player_db_export Ajax for #' + player_id ); $.post(ajaxurl, { action: 'fv_player_db_export', playerID : player_id, nonce : $element.data('nonce'), cookie: encodeURIComponent(document.cookie), }, function(json_export_data) { var overlay = overlay_show('export'); overlay.find('textarea').val( $('
').text(json_export_data).html() ); }).fail( function( jqXHR, textStatus, errorThrown ) { overlay_show('message', 'An unexpected error has occurred while exporting player #' + player_id + ': ' + errorThrown + '

Please try again.'); }); }, onClosed : overlay_hide, onOpen: lightbox_open } ); return false; }); $doc.on( 'click', '.fv-player-import', function(e) { e.preventDefault(); $.fv_player_box( { onComplete : function() { overlay_show('import'); }, onClosed : overlay_hide, onOpen: lightbox_open } ); return false; }); $doc.on( 'click', '.fv-player-remove', function() { jQuery(this) .addClass('fv-player-remove-confirm') .removeClass('fv-player-remove') .html('Are you sure?') .one('mouseleave', function() { jQuery(this) .removeClass('fv-player-remove-confirm') .addClass('fv-player-remove') .html('Delete'); }); return false; }); $doc.on( 'click', '.fv-player-remove-confirm', function() { let $element = $(this), $row_actions = $element.closest( '.row-actions' ), $element_td = $element.parent(), $spinner = $('
'), player_id = $element.data('player_id'); $element_td.find('a, span').hide(); $element.after($spinner); // Make sure the row actions do not show on hover only, but always appear to make sure the spinner remains visible $row_actions.css( 'left', 0 ); debug_log('Running fv_player_db_remove Ajax for #' + player_id ); jQuery.post(ajaxurl, { action: "fv_player_db_remove", nonce: $element.data('nonce'), playerID: player_id }, function(rows_affected){ if (!isNaN(parseFloat(rows_affected)) && isFinite(rows_affected)) { // remove the deleted player's row $element.closest('tr').hide('slow', function() { jQuery(this).remove(); }); } else { $spinner.remove(); alert(rows_affected); $element_td.find('span, a:not(.fv-player-remove-confirm)').show(); } }).fail(function() { $spinner.remove(); $element.html('Error'); $element_td.find('span, a:not(.fv-player-remove-confirm)').show(); }); return false; }); $doc.on( 'click', '.fv-player-clone', function() { var $element = jQuery(this), $spinner = $('
 
'); $element .hide() .after($spinner); debug_log('Running fv_player_db_clone Ajax.'); $.post(ajaxurl, { action: "fv_player_db_clone", nonce: $element.data('nonce'), playerID: $element.data('player_id') }, function(playerID){ if (playerID != '0' && !isNaN(parseFloat(playerID)) && isFinite(playerID)) { // add the inserted player's row $.get( fv_player_editor_conf.admin_url + '&id=' + playerID, function (response) { $('#the-list tr:first').before(jQuery(response).find('#the-list tr:first')); $spinner.remove(); $element.show(); }).fail(function() { $spinner.remove(); $element.show(); }); } else { $spinner.remove(); $element.show(); alert(playerID); // show respone message } }).fail(function() { $spinner.remove(); $element.show(); alert('Error'); }); return false; }); } // Gutenberg style checkboxes $el_editor.on( 'change', '.components-form-toggle input[type=checkbox]', function() { var wrap = $(this).closest('.components-form-toggle'), checked = $(this).prop('checked'), name = $(this).attr('name').replace( /fv_wp_flowplayer_field_/, '' ); checkbox_toggle_worker(wrap, name, checked); }); $el_editor.on('change input', '.components-text-control__input, .components-select-control__input', function() { var input = jQuery(this), parent = input.closest('.fv-player-editor-children-wrap'), name = input.attr('name').replace( /fv_wp_flowplayer_field_/, '' ), wrap = input.parents( '.fv-player-editor-field-wrap-' + name ); text_and_select_worker( input, parent, name, wrap ); }); /* * Intro view text input and button */ // Copy hero input value to the first video src and trigger event for save & preview $('[name=hero-src]').on('change keyup', function() { $('#playlist-hero .fv-player-editor-notice.notice-use-ui').hide(); let input = $( this ), value = input.val().trim(), is_valid_url = false; if( value ) { if( value.match( /https?:\/\// ) ) { for (var vtype in window.fv_player_editor_matcher) { if (window.fv_player_editor_matcher[vtype].matcher.exec(value) !== null) { is_valid_url = true; break; } } } if( $( '#playlist-hero:visible' ).length ) { $('#playlist-hero .fv-player-editor-notice.notice-url-format').toggle( !is_valid_url ); } else { $('#fv-player-shortcode-editor-preview-no .fv-player-editor-notice.notice-url-format').toggle( !is_valid_url ); } } if( !is_valid_url ) { return false; } get_hero_src_field( $(this) ).val( value ).trigger( 'keyup' ); }); $('.button-hero').on('click', function() { // click on the media library button next to that input get_hero_src_field( $(this) ).closest('.components-base-control__field').find('.add_media').trigger('click'); }); /* * NAV TABS */ $('.fv-player-tabs-header a').on( 'click', function(e) { e.preventDefault(); $('.fv-player-tabs-header a').removeClass('nav-tab-active'); $(this).addClass('nav-tab-active') $('.fv-player-tabs > .fv-player-tab').hide(); $('.' + $(this).data('tab')).show(); current_editor_tab = $(this).data('tab'); }); /* * Select playlist item * keywords: select item */ $doc.on('click','.fv-player-editor-playlist-item .configure-video, .fv-player-editor-playlist-item .fvp_item_video-thumbnail', function() { if( fv_player_editor_conf.hide && fv_player_editor_conf.hide.indexOf( 'configure-video' ) > 0 ) { return false; } playlist_item_show( $(this).parents('.fv-player-editor-playlist-item').attr('data-index') ); return false; }); /* * Show edit input * keywords: edit playlist items edit playlist items */ $doc.on('click','.fv-player-tab-playlist .fv-player-editor-playlist-item .fvp_item_video-filename', function(e) { e.stopPropagation(); var $parent = $(e.target).closest('[data-index]'), filename = $parent.find('.fvp_item_video-filename'), wrap = $parent.find('.fvp_item_video-title-wrap'), input = $parent.find('.fvp_item_video-edit-input'); wrap.hide(); input.val(filename.text()).show().focus(); is_editing_playlist_item_title = true; }); // Clicking anywhere it the editor should close the playlist item title editing $el_editor.on( 'click', function(e) { if( is_editing_playlist_item_title ) { // Except the title input box if( jQuery(e.target).hasClass('fvp_item_video-edit-input') ) { return; } is_editing_playlist_item_title = false; title_editor_close(); } }); /* * Edit title * keywords: edit title playlist items edit title playlist items */ $doc.on('keyup','.fv-player-tab-playlist .fv-player-editor-playlist-item .fvp_item_video-edit-input', function(e) { e.stopPropagation(); if( e.key == "Enter" ) { title_editor_close(); return; } var $parent = $(e.target).parents('[data-index]'), index = $parent.attr('data-index'), video_tab = get_tab(index, 'video-files'), new_title = jQuery(this).val(); $parent.find('.fvp_item_video-filename').text( new_title ); get_field('title', video_tab).val( new_title ).trigger('keyup'); }); /* * Remove playlist item * keywords: delete playlist items remove playlist items */ $doc.on('click','.fv-player-tab-playlist .fv-player-editor-playlist-item .fvp_item_remove', function(e) { e.stopPropagation(); if( !confirm('Would you like to remove this video?') ) { return false; } var playlist_row = $(e.target).parents('[data-index]'), index = playlist_row.attr('data-index'), id = get_tab(index,'video-files').attr('data-id_video'); deleted_videos.push(id); playlist_row.remove(); get_tab(index,'video-files').remove(); get_tab(index,'subtitles').remove(); get_tab(index,'cues').remove(); // Keep data-index in order playlist_index(); // if no playlist item is left, add a new one // TODO: Some better way? Like do it after the data is saved if( !jQuery('.fv-player-tab-subtitles [data-playlist-item][data-index]').length ){ // Required, with this it actually saves the change! playlist_item_add(); if( fv_player_editor_conf.frontend ) { reset_preview(); playlist_show(); } else { playlist_item_show(0); } } $doc.trigger('fv_player_editor_item_delete'); return false; }); /* * Sort playlist */ $('.fv-player-tab-playlist #fv-player-editor-playlist').sortable({ start: function() { store_rtmp_server = get_field( 'rtmp', get_tab('first','video-files') ).val(); }, update: playlist_sortable_update, axis: 'y', handle: '.fv-player-editor-playlist-move-handle', containment: ".fv-player-tab-playlist" }); $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) { var button = $(e.target), item = button.closest('[data-index]'); if( button.hasClass('fv-player-editor-playlist-move-up') ) { item.fadeOut( 250, function() { item.insertBefore( item.prev() ); item.fadeIn( 250, playlist_sortable_update ); }); } else { item.fadeOut( 250, function() { item.insertAfter( item.next() ); item.fadeIn( 250, playlist_sortable_update ); }); } }); function playlist_sortable_update() { $doc.trigger('fv-player-editor-sortable-update'); var new_sort = []; $('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').each(function(){ var index = $(this).attr('data-index'), video_tab_item = get_tab(index,'video-files'), subtitle_tab_item = get_tab(index,'subtitles'); new_sort.push({ video_tab_item : video_tab_item.clone(), subtitle_tab_item : subtitle_tab_item.clone() }); video_tab_item.remove(); subtitle_tab_item.remove(); }); $.each(new_sort, function(k,v) { $('.fv-player-tab-video-files').append(v.video_tab_item); $('.fv-player-tab-subtitles').append(v.subtitle_tab_item); }); get_field( 'rtmp', get_tab('first','video-files') ).val( store_rtmp_server ); playlist_index(); $doc.trigger('fv_player_editor_playlist_sort'); } /* * Uploader */ var fv_flowplayer_uploader; var fv_flowplayer_uploader_button; $doc.on( 'click', '#fv-player-shortcode-editor .components-button.add_media, .fv-player-gutenberg-media', fv_flowplayer_uploader_init ); /** * In order for Media Library to work we need: * - set fv_flowplayer_uploader_button * - add .fv_flowplayer_target to the input field that will be updated */ function fv_flowplayer_uploader_init(e) { e.preventDefault(); fv_flowplayer_uploader_button = jQuery(this); var el_input = fv_flowplayer_uploader_button.closest('.components-base-control__field').find('[name=' + fv_flowplayer_uploader_button.data('target') + ']'); // Fallback to previous input, used in FV Player Block for the "Settings" sidebar, also called "Inspector" if ( ! el_input.length ) { el_input = fv_flowplayer_uploader_button.prev('.components-panel__row').find('input'); } /** * Mark the input as target. * * If using Gutenberg block .fv_flowplayer_target won't be added anywhere, but it does not matter. * We update the block properites directly. */ if( el_input.length ) { // Remove target from the previous field jQuery('.fv_flowplayer_target').removeClass('fv_flowplayer_target' ); el_input.addClass('fv_flowplayer_target'); } fv_flowplayer_uploader_open(); } function fv_flowplayer_uploader_open() { debug_log( 'Opening Media Library...' ); //If the uploader object has already been created, reopen the dialog if (fv_flowplayer_uploader) { fv_flowplayer_uploader.open(); return; } //Extend the wp.media object fv_flowplayer_uploader = wp.media.frames.file_frame = wp.media({ title: 'Add Video', button: { text: 'Choose' }, multiple: false }); fv_flowplayer_uploader.on('open', function() { $( document ).trigger( "mediaBrowserOpen" ); jQuery('.media-router .media-menu-item').eq(0).trigger('click'); jQuery('.media-frame-title h1').text(fv_flowplayer_uploader_button.text()); // Hide Media Library tabs which are not allowed if( fv_player_editor_conf.library ) { let libraries = fv_player_editor_conf.library.split(/,/), library_found = false; $( '.media-router .media-menu-item' ).each( function( i, el ) { let found = false; $( libraries ).each( function( v, library ) { if( $(el).attr('id').match(library) ) { found = library_found = true; $(el).trigger( 'click' ); } }); if( !found ) { $(el).hide(); } }); // At least close the Media Library if the desired that was not found // TODO: It should not open at all if the required library is not found if ( ! library_found ) { debug_log('Libraries '+fv_player_editor_conf.library+' not found!'); fv_flowplayer_uploader.close(); } } }); //When a file is selected, grab the URL and set it as the text field's value fv_flowplayer_uploader.on('select', function() { var attachment = fv_flowplayer_uploader.state().get('selection').first().toJSON(); var target_element = $('.fv_flowplayer_target'); // Update the HTML input field if ( target_element.length ) { target_element.val(attachment.url).trigger('change').trigger('keyup'); target_element.removeClass('fv_flowplayer_target' ); } // Did we open the media library from within the block? if ( fv_flowplayer_uploader_button.hasClass( 'fv-player-gutenberg-media' ) ) { var clientId = null; if ( typeof wp !== 'undefined' && wp.data && wp.data.select( 'core/block-editor' ) ) { clientId = wp.data.select( 'core/block-editor' ).getSelectedBlockClientId(); } if ( ! clientId ) { clientId = jQuery('.is-selected[data-type="fv-player-gutenberg/basic"]').data('block'); } if ( ! clientId ) { 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'); } if ( clientId ) { debug_log( 'Media library updating block attributes.' ); wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes(clientId, { src: attachment.url }); } } if( attachment.type == 'video' ) { // TODO: Fill video title } else if( attachment.type == 'image' ) { if( attachment.id ) { // update splash attachent id get_field( 'splash_attachment_id', true ).val(attachment.id); } if( typeof(fv_flowplayer_set_post_thumbnail_id) != "undefined" ) { if( jQuery('#remove-post-thumbnail').length > 0 ) { return; } debug_log('Running set-post-thumbnail Ajax.'); jQuery.post(ajaxurl, { action:"set-post-thumbnail", post_id: fv_flowplayer_set_post_thumbnail_id, thumbnail_id: attachment.id, _ajax_nonce: fv_flowplayer_set_post_thumbnail_nonce, cookie: encodeURIComponent(document.cookie) }, function(str){ if ( str == '0' ) { alert( setPostThumbnailL10n.error ); } else { jQuery('#postimagediv .inside').html(str); jQuery('#postimagediv .inside #plupload-upload-ui').hide(); } } ); } } }); //Open the uploader dialog fv_flowplayer_uploader.open(); } template_playlist_item = jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').parent().html(); template_video = get_tab('first','video-files').parent().html(); template_subtitles_tab = jQuery('.fv-player-tab-subtitles').html(); /* * End of playlist Actions */ jQuery('#fv_wp_flowplayer_field_end_actions').on( 'change', show_end_actions ); /* * Preview iframe dialog resize */ $doc.on('fvp-preview-complete',function() { $el_preview.attr('class','preview-show'); // If editor was in the intro mode, we show the playlist and enable the full-editor if( $el_editor.hasClass('is-intro') ) { playlist_show(); $el_editor.removeClass('is-intro'); $('[name=hero-src]').val(''); } // If editor was in the frontend mode and adding playlist item we hide the playlist hero item now // and instead show the playlist item row if( $( '#playlist-hero:visible' ).length ) { playlist_hero_hide(); } }); /* * Video share option */ // TODO: Check jQuery('#fv_wp_flowplayer_field_share').on( 'change', function(){ var value = jQuery(this).val(); switch(value){ case 'Custom': jQuery("#fv_wp_flowplayer_field_share_custom").show(); break; default: jQuery("#fv_wp_flowplayer_field_share_custom").hide(); break; } }); $doc.on("change", "#fv-player-shortcode-editor-right input, #fv-player-shortcode-editor-right select", save ); $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) { clearTimeout(int_keyup); int_keyup = setTimeout( function() { save(e); }, fv_player_editor_conf.keyup_throttle ); }); $doc.on('fv_flowplayer_shortcode_new fv-player-editor-non-db-shortcode', function() { insert_button_toggle(true); copy_player_button_toggle(true); ajax_saving = false; }); $doc.on('fv_player_editor_player_loaded', function() { ajax_saving = false; is_unsaved = false; }); $doc.on('fv_flowplayer_player_editor_reset', function() { ajax_saving = true; is_unsaved = true; has_draft_status = true; //is_draft_changed = false; }); $doc.on('fv_player_editor_playlist_sort', save ); $doc.on('fv_player_editor_item_delete', save ); $doc.on('fv_player_editor_language_delete', save ); /* * @param {object} [e] The event handle is invoked by input change event */ function save(e) { // "ajax_saving" is implicitly set to true to make sure we wait with any saving until // all existing player's data are loaded and filled into inputs if ( ajax_saving ) { return; } // Skips the interface toggles (Advanced Settings, RTMP) if( e && $(e.target).hasClass('no-data') ) { return; } if( e && $(e.target).data('no-reload') ) { prevent_reload_for_current_save = true; } else { prevent_reload_for_current_save = false; } var ajax_data = build_ajax_data(true), db_data_loading = true; for ( var item in ajax_data.videos ) { // we have video data already loaded from DB, // ... this is the only way to see if we actually have // at least a single object key while preserving // browsers compatibility for browsers without support // for Object.keys() db_data_loading = false; break; } if ( db_data_loading || !ajax_data.videos ) { // editing a player and still waiting for the videos to load from DB return; } if( ajax_save_previous && JSON.stringify(ajax_data) == JSON.stringify(ajax_save_previous) ) { debug_log('No changes to save.'); return; } if( is_saving ) { debug_log('Still saving!'); next = true; return; } ajax_save_previous = ajax_data; ajax(ajax_data); } function ajax( data ) { ajax_save_this_please = data; } function error(msg) { is_saving = false; $el_editor.find('.button-primary').prop('disabled', false); overlay_show('message', 'An unexpected error has occurred. Please try again. '+msg, true ); } setInterval( function() { if ( !ajax_save_this_please || is_loading_meta_data ) return; // show error overlay if we have errors var err = has_errors(); if ( err ) { add_notice( 'error', err ); return; } var what_is_saving = ajax_save_this_please; is_saving = true; insert_button_toggle_disabled(true); el_spinner.show(); remove_notices(); ajax_save_this_please = set_control_fields( ajax_save_this_please ); debug_log('Running fv_player_db_save Ajax.', ajax_save_this_please.update ); let time_start_save = performance.now(); $.ajax({ type: 'POST', url: ajaxurl + '?action=fv_player_db_save', data: JSON.stringify( { data: ajax_save_this_please, nonce: fv_player_editor_conf.edit_nonce, } ), contentType: 'application/json', dataType: 'json', success: function(response) { debug_log( 'Finished fv_player_db_save Ajax in ' + debug_time( time_start_save ) + '.' ); if( response.error ) { if( response.fatal_error ) { debug_log( 'Fatal error in fv_player_db_save: ' + response.fatal_error ); var json_export_data = jQuery('
').text(JSON.stringify(what_is_saving)).html(); var overlay = overlay_show('error_saving'); overlay.find('textarea').val( response.error + '\n\nJSON data:\n\n' + $('
').text(json_export_data).html() ); jQuery('#fv_player_copy_to_clipboard').select(); } else { add_notice( 'error', response.error ); debug_log( 'Error in fv_player_db_save: ' + response.error ); } el_spinner.hide(); is_saving = false; return; } let time_start_display = performance.now(); debug_log('player ID after save: '+response.id); current_player_object = response; current_player_db_id = response.id; try { $(response.videos).each( function(k,v) { var item = $('.fv-player-playlist-item').eq(k); if( !item.data('id_video') ) { item.attr('data-id_video',v.id); } if( k == current_video_to_edit ) { debug_log('current_video_db_id after save: '+v.id); current_video_db_id = v.id; } /* * The video saving might fetch the video duration, splash screen and title... * So we fill that in */ // TODO: Populate chapters, error, is_live, is_audio, encryption key var video_tab = get_tab( k, 'video-files' ), subtitles_tab = get_tab( k, 'subtitles' ), splash_field = get_field( 'splash', video_tab ), splash_attachment_id_field = get_field( 'splash_attachment_id', video_tab ), title_field = get_field( 'title', video_tab ), auto_splash = get_playlist_video_meta_value( 'auto_splash', k ), auto_caption = get_playlist_video_meta_value( 'auto_caption', k ); if( get_field('auto_splash', video_tab ).val() == '0' ) { auto_splash = false; } if( get_field('auto_caption', video_tab ).val() == '0' ) { auto_caption = false; } if( v.splash && ( !splash_field.val() || auto_splash ) ) { splash_field.val( v.splash ); if( v.splash_attachment_id && !splash_attachment_id_field.val() ) { splash_attachment_id_field.val( v.splash_attachment_id ) } } // Populate video meta fields for which the video checking on video save has added value Object.keys( window.fv_player_editor_fields ).forEach( function( field_name ) { if ( 'video_meta' == window.fv_player_editor_fields[ field_name].store ) { let field_value = get_playlist_video_meta_value( field_name, k ); if( field_value ) { if ( !get_field( field_name, video_tab ).val() ) { get_field( field_name, video_tab ).val( field_value ); } if ( !get_field( field_name, subtitles_tab ).val() ) { get_field( field_name, subtitles_tab ).val( field_value ); } } } } ); if( auto_splash ) { get_field('auto_splash', video_tab ).val( auto_splash ); } if( v.title && ( !title_field.val() || auto_caption ) ) { title_field.val( v.title ); text_and_select_worker( title_field, title_field.closest('.fv-player-editor-children-wrap'), 'title', title_field.parents( '.fv-player-editor-field-wrap-title' ) ); } if( auto_caption ) { get_field('auto_caption', video_tab ).val( auto_caption ); } // 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 if( current_video_to_edit == k ) { show_video_details(k); show_stream_fields_worker(k); show_playlist_not_supported(k); } }); // If we are in playlist view, we refresh the list too if( current_video_to_edit == -1 ) { playlist_refresh(); } /** * Allow plugins to fill in the hidden fields with data coming back from save response. * These are the fields which hold ID of any additional items created, like PPV product IDs. * Their IDs need to be taken into consideration right away as otherwise the PPV products might multiple * if you make more changes to the price and save again before the initial save is done. */ $doc.trigger( 'fv_flowplayer_player_meta_load_high_priority', [ response ] ); debug_log( 'Finished populating editor fields for ' + response.videos.length + ' videos in ' + debug_time( time_start_display ) + '.' ); // Did the data change while saving? if( next ) { debug_log('There is more to save...'); if ( is_unsaved ) { init_saved_player_fields(); } ajax( build_ajax_data(true) ); next = false; } else { is_saving = false; insert_button_toggle_disabled(false); el_spinner.hide(); add_notice( 'success', 'Saved!', 2500 ); // close the overlay, if we're waiting for the save if (overlay_close_waiting_for_save) { // add this player's ID into players that no longer need an edit lock if (current_player_db_id > 0) { edit_lock_removal[current_player_db_id] = 1; } overlay_close_waiting_for_save = false; $.fn.fv_player_box.close(); } else if ( typeof( response.html ) != "undefined" && !prevent_reload_for_current_save ) { if( response.html ) { // auto-refresh preview el_preview_target.html( response.html ); $doc.trigger('fvp-preview-complete'); } else { reset_preview(); } } prevent_reload_for_current_save = false; // if we're creating a new player, hide the Save / Insert button and // add all the data and inputs to page that we need for an existing player if ( is_unsaved ) { copy_player_button_toggle(false); init_saved_player_fields(); is_unsaved = false; //is_draft_changed = false; ajax_saving = false; ajax_save_this_please = false; } // Output the shortcode into the pre-configured output field if( fv_player_editor_conf.field_selector ){ insert_shortcode( '[fvplayer id="'+current_player_db_id+'"]'); } // Allow plugins to fill in the field with data coming back from save response $doc.trigger( 'fv_flowplayer_player_meta_load', [ response ] ); // Set the current data as previous to let auto-saving detect changes // For new player this will have video and player IDs ajax_save_previous = build_ajax_data(true); } } catch(e) { error(e); } }, error: function( jqXHR, textStatus, errorThrown) { add_notice( 'error', '

Error saving changes: ' + errorThrown + ': ' + jqXHR.responseText + '

' ); debug_log( 'HTTP Error in fv_player_db_save: ' + errorThrown + ': ' + jqXHR.responseText ); el_spinner.hide(); is_saving = false; } }); ajax_save_this_please = false; }, 1500 ); editor_init(); var $body = jQuery('body'); $body.on('focus', '#fv_player_copy_to_clipboard', function() { this.select(); }); jQuery('.fv-player-editor-wrapper').each( function() { fv_show_video( jQuery(this) ) }); // show last add more button only $doc.on( 'fv_flowplayer_shortcode_insert', '.fv-player-editor-field', function() { fv_load_video_preview( jQuery(this).parents('.fv-player-editor-wrapper')); } ); /* Custom Videos feature TODO: Test */ function fv_show_video( wrapper ) { if( wrapper.find('.fv-player-editor-field').val() ) { wrapper.find('.edit-video').show(); wrapper.find('.add-video').hide(); } else { wrapper.find('.edit-video').hide(); wrapper.find('.add-video').show(); wrapper.find('.fv-player-editor-preview').html(''); } jQuery('[data-key='+wrapper.data('key')+'] .fv-player-editor-more').last().show(); // show last add more button only } /* Custom Videos feature TODO: Test */ function fv_remove_video( id ) { $( '#widget-widget_fvplayer-'+id+'-text' ).val(""); fv_show_video(id); $('#fv_edit_video-'+id+' .video-preview').html(''); } /* Custom Videos feature TODO: Test */ function fv_load_video_preview( wrapper ) { var shortcode = $(wrapper).find('.fv-player-editor-field').val(); var indicator = $("
").appendTo('.fp-playlist-external'); if( shortcode && shortcode.length === 0 ) { return false; } shortcode = shortcode.replace( /(width=[\'"])\d*([\'"])/, "$1320$2" ); // 320 shortcode = shortcode.replace( /(height=[\'"])\d*([\'"])/, "$1240$2" ); // 240 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); $.get(url, function(response) { wrapper.find('.fv-player-editor-preview').html( jQuery('#wrapper',response ) ); $doc.trigger('fvp-preview-complete', [ shortcode, wrapper.data('key'), wrapper ] ); indicator.remove(); } ); fv_show_video(wrapper); } $doc.on('click','.fv-player-editor-remove', function() {console.log('.fv-player-editor-remove'); var wrapper = $(this).parents('.fv-player-editor-wrapper'); if( $('[data-key='+wrapper.data('key')+']').length == 1 ) { // if there is only single video wrapper.find('.fv-player-editor-field').val(''); fv_show_video(wrapper); } else { wrapper.remove(); jQuery('.fv-player-editor-wrapper').each( function() { fv_show_video( jQuery(this) ) }); // show last add more button only } return false; }); $doc.on('click','.fv-player-editor-more', function() { var wrapper = $(this).parents('.fv-player-editor-wrapper'); var new_wrapper = wrapper.clone(); new_wrapper.find('.fv-player-editor-field').val(''); fv_show_video(new_wrapper); new_wrapper.insertAfter( $('[data-key='+wrapper.data('key')+']:last') ); // insert after last of the kind $(this).hide(); return false; }); $doc.on( 'click', '.fv-player-shortcode-copy', function() { var button = $(this); fv_player_clipboard( $(this).parents('tr').find('.fv-player-shortcode-input').val(), function() { button.html('Coppied to clipboard!'); setTimeout( function() { button.html('Copy Shortcode'); }, 1000 ); }, function() { button.html('Error'); } ); return false; }); $doc.on('click', '#fv-player-editor-copy_player-overlay .attachment', function() { let item = $(this), sidebar = $('#fv-player-editor-copy_player-overlay .media-sidebar'), attachment_details = sidebar.find( '.attachment-details' ), details = item.data('details'); $( '#fv-player-editor-copy_player-overlay .attachment' ).removeClass( 'selected details' ); item.addClass('selected details'); attachment_details.find('.filename').text( '#' + details.id + '. '+ details.player_name ); attachment_details.find('.uploaded').text( details.date_created ); attachment_details.find('.videos-list').html( '
  • ' + details.video_titles.join('
  • ' ) + '
' ); let ul = $('
    '); $.each( details.embeds, function( k, v ) { let type = v.post_type != 'post' ? ' (' + v.post_type + ')' : '', status = v.post_status != 'publish' ? ' (' + v.post_status + ')' : '', taxonomies_and_terms = []; if( v.taxonomies ) { $.each( v.taxonomies, function( taxonomy, taxonomy_details ) { let taxonomy_terms = []; $.each( taxonomy_details.terms, function( k, term ) { taxonomy_terms.push( term.name ); } ); taxonomies_and_terms.push( taxonomy_details.label + ': ' + taxonomy_terms.join( ', ' ) ); } ); } taxonomies_and_terms = '
    • ' + taxonomies_and_terms.join( '
    • ' ) + '
    '; ul.append( '
  • ' + v.post_title + '' + type + status + taxonomies_and_terms + '
  • ') } ); attachment_details.find('.posts-list').html('').append( ul ); attachment_details.show(); $( '#fv-player-editor-copy_player-overlay .button-primary' ).prop( 'disabled', false ); }); $doc.on('click', '#fv-player-editor-copy_player-overlay .button-primary', function() { $el_editor.find('.button-primary').text('Insert'); let selected = $( '#fv-player-editor-copy_player-overlay .attachment.selected' ); if( selected.length > 0 ) { editor_open( selected.data('details').id ); } }); $doc.on('click', '.fv_player_field_insert-button', function() { if (is_saving || ajax_save_this_please) { // for some reason, clicking on already-disabled primary button re-enables it, // so we'll just need to disable it again here insert_button_toggle_disabled(true); } else { // make sure we mark this player as published in the DB has_draft_status = false; editor_submit(); } }); $doc.on('click', '.playlist_add', function() { // If it's front-end mode do not add new item instantly, but show input and a button if( fv_player_editor_conf.frontend ) { console.log( 'is_playlist_hero_editing', is_playlist_hero_editing ); if( is_playlist_hero_editing ) { $('#playlist-hero .fv-player-editor-notice.notice-use-ui').show(); } else { playlist_hero_show(); } } else { playlist_item_add(); } return false; }); $doc.on('click', '.playlist_edit', function() { if ( !jQuery(this).hasClass('disabled') ) { playlist_show(); reload_preview( current_video_to_edit ); } return false; }); // prevent closing of the overlay if we have unsaved data // unfortunately there is no event for this which we could use // TODO: Now we can do this propery since we dropped Colorbox! $.fn.fv_player_box.oldClose = $.fn.fv_player_box.close; $.fn.fv_player_box.close = function() { // don't close editor if we have errors showing, otherwise we'd just overlay them by an infinite loader if ( has_errors() ) { return; } /*if (is_draft && is_draft_changed && !window.confirm('You have unsaved changes. Are you sure you want to close this dialog and loose them?')) { return false; }*/ // TODO: Why !is_unsaved? if ( ( !is_unsaved || is_saving ) && has_draft_status && !is_fv_player_screen( editor_button_clicked ) ) { // TODO: change into notification bubble alert('Your new player was saved as a draft. To reopen it, open the editor again and use the "Pick existing player" button.'); } // prevent closing if we're still saving the data if ( ajax_save_this_please || is_saving || is_loading_meta_data ) { // if we already have the overlay changed, bail out if (overlay_close_waiting_for_save) { return; } overlay_close_waiting_for_save = true; $('.fv-wp-flowplayer-notice-small, .fv-player-shortcode-editor-small-spinner').hide(); overlay_show('loading'); // call fv_wp_flowplayer_submit() which will create a repeating task // that will check for all meta data being loaded, // so we can auto-close this overlay once that's done editor_submit(); return; } // close the dialog if confirmed // $.fn.fv_player_box.oldClose(); // reset variables is_unsaved = true; has_draft_status = true; //is_draft_changed = false; // manually invoke a heartbeat to remove an edit lock immediatelly if (current_player_db_id > -1 && window.wp && wp.heartbeat && wp.heartbeat.connectNow ) { // do this asynchronously to allow our cleanup procedures set lock removal data for the next hearbeat setTimeout(wp.heartbeat.connectNow, 500); } } /* Loads and displays a list of all players in a dropdown. */ $doc.on('click', '.copy_player', function() { // show loader overlay_show('loading'); debug_log('Running fv_player_db_retrieve_all_players_for_dropdown Ajax.'); $.post(ajaxurl, { action: 'fv_player_db_retrieve_all_players_for_dropdown', nonce: fv_player_editor_conf.search_nonce, }, show_players ).fail( function ( jqXHR, textStatus, errorThrown ) { overlay_show('message', 'An unexpected error has occurred while loading players: ' + errorThrown + '

    Please try again.'); }); return false; }); let do_search = false, is_searching = false, search_val = false; $doc.on('keyup', '#fv-player-editor-copy_player-overlay [name=players_selector]', function() { search_val = $(this).val(); do_search = true; }); setInterval( function() { if( do_search ) { if( is_searching ) { // Try again later return; } $.post(ajaxurl, { action: 'fv_player_db_retrieve_all_players_for_dropdown', nonce: fv_player_editor_conf.search_nonce, search: search_val }, show_players ).fail( function ( jqXHR, textStatus, errorThrown ) { overlay_show('message', 'An unexpected error has occurred while searching in player data: ' + errorThrown + '

    Please try again.'); }); is_searching = true; do_search = false; } }, 1000 ); function show_players(json_data) { if( !json_data.success ) { overlay_show('message', json_data.data ); return; } is_searching = false; let overlay = overlay_show('copy_player'), list = overlay.find('.attachments'), button = overlay.find('.button-primary'); button.prop( 'disabled', true ); list.html(''); for (var i in json_data.players) { let data = json_data.players[i], image = $( data.thumbs[0] ).find('img'), player_name = data.player_name || data.video_titles.join( ', ' ); image.removeAttr('width'); let item = $('
  • '); item.find('.thumbnail').append( image ); item.find('.thumbnail .filename > div').html( player_name ); if( image.length == 0 ) { item.find('.thumbnail .filename').removeClass('hidden'); } item.attr( 'data-details', JSON.stringify( data ) ); list.append( item ); } show_players_resize(); } $( window ).on( 'resize', show_players_resize ); function show_players_resize() { let player_browser = $( '#fv-player-editor-copy_player-overlay .attachments-browser'), player_browser_list = player_browser.find( '.attachments' ), idealColumnWidth = $( window ).width() < 640 ? 135 : 150; player_browser.attr( 'data-columns', Math.min( Math.round( player_browser_list.width() / idealColumnWidth ), 12 ) || 1 ); } }); /** * Initializes shortcode, removes playlist items, hides elements, figures out * which actual field is edited - post editor, widget, etc. */ function editor_init() { // if error / message overlay is visible, hide it overlay_hide(); remove_notices(); jQuery('#fv_wp_flowplayer_field_player_name').show(); jQuery('#player_id_top_text').html(''); /** * Get the active post editor instance */ // is there a Custom Video field or Gutenberg field next to the button? var field = $(editor_button_clicked).parents('.fv-player-editor-wrapper, .fv-player-gutenberg').find('.fv-player-editor-field'), elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ), widget = jQuery('#widget-widget_fvplayer-'+widget_id+'-text'); if( fv_player_editor_conf.field_selector ){ var custom_field_selector = jQuery(fv_player_editor_conf.field_selector) // If the pre-configured field was not failed it's a big deal! if( !custom_field_selector.length ){ alert( 'FV Player Editor: Field '+fv_player_editor_conf.field_selector+' not found!' ); } editor_content = custom_field_selector.val(); // No need for insert button insert_button_toggle(false); } else if( field.length ) { if (field[0].tagName != 'TEXTAREA' && !field.hasClass('attachement-shortcode')) { field = field.find('textarea').first(); } editor_content = jQuery(field).val(); } else if( elementor_field.length ) { editor_content = elementor_field.val(); } else if( widget.length ){ editor_content = widget.val(); } else if( document.querySelector('.CodeMirror') && typeof(CodeMirror) !== 'undefined' ) { instance_code_mirror = document.querySelector('.CodeMirror').CodeMirror; } else if( typeof(FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length){ editor_content = jQuery('#content:not([aria-hidden=true])').val(); } else if( typeof tinymce !== 'undefined' && typeof tinymce.majorVersion !== 'undefined' && typeof tinymce.activeEditor !== 'undefined' && tinymce.majorVersion >= 4 ){ instance_tinymce = tinymce.activeEditor; } else if( typeof tinyMCE !== 'undefined' ) { instance_tinymce = tinyMCE.getInstanceById('content'); } else if(typeof(FCKeditorAPI) !== 'undefined' ){ instance_fp_wysiwyg = FCKeditorAPI.GetInstance('content'); } reset_preview(); jQuery('.fv-player-tab-video-files [data-playlist-item]').each( function(i,e) { if( i == 0 ) return; jQuery(e).remove(); } ); jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').each( function(i,e) { if( i == 0 ) return; jQuery(e).remove(); } ); jQuery('.fv-player-tab-subtitles').html(template_subtitles_tab); jQuery('.fv_wp_flowplayer_field_subtitles_lang').val(0); /** * TABS */ jQuery('#fv-player-shortcode-editor a[data-tab=fv-player-tab-playlist]').hide(); jQuery('#fv-player-shortcode-editor a[data-tab=fv-player-tab-video-files]').trigger('click'); jQuery('.nav-tab').show; reset_editor_ids(); editing_video_details = true; $el_editor .addClass( 'is-singular' ) .addClass( 'is-singular-active' ) .removeClass( 'is-playlist-active' ) .removeClass( 'is-playlist' ); //hide empy tabs hide tabs jQuery('.fv-player-tab-playlist').hide(); jQuery('.fv-player-playlist-item-title').html(''); jQuery('.fv-player-tab-video-files [data-playlist-item]').show(); jQuery('.playlist_edit').html(jQuery('.playlist_edit').data('create')).removeClass('button-primary').addClass('button'); tabs_refresh(); playlist_buttons_disable(false); playlist_buttons_toggle(true); set_embeds(''); el_preview_target.html(''); if( typeof(fv_player_shortcode_editor_ajax) != "undefined" ) { fv_player_shortcode_editor_ajax.abort(); } $doc.trigger('fv-player-editor-init'); hide_inputs(); $el_editor.show(); } /** * Checks all the input fields and created the JavaScript object. * Works when saving and also previewing. */ function build_ajax_data( give_it_all ) { var $tabs = $el_editor.find('.fv-player-tab'), regex = /((fv_wp_flowplayer_field_|fv_wp_flowplayer_hlskey|fv_player_field_ppv_)[^ ]*)/g, data = {'video_meta' : {}, 'player_meta' : {}}, end_of_playlist_action = jQuery('#fv_wp_flowplayer_field_end_actions').val(), single_video_showing = !give_it_all && editing_video_details; // special processing for end video actions if (end_of_playlist_action && end_of_playlist_action != 'Nothing') { switch (end_of_playlist_action) { case 'redirect': data['fv_wp_flowplayer_field_end_action_value'] = jQuery('#fv_wp_flowplayer_field_redirect').val(); break; case 'popup': data['fv_wp_flowplayer_field_end_action_value'] = jQuery('#fv_wp_flowplayer_field_popup_id').val(); break; case 'email_list': data['fv_wp_flowplayer_field_end_action_value'] = jQuery('#fv_wp_flowplayer_field_email_list').val(); break; } } // add playlist name data['fv_wp_flowplayer_field_player_name'] = jQuery('#fv_wp_flowplayer_field_player_name').val(); // add post ID manually here, as it's a special meta key insertUpdateOrDeletePlayerMeta({ data: data, meta_section: 'player', meta_key: 'post_id', element: jQuery('#fv_wp_flowplayer_field_post_id')[0], handle_delete: false }); // trigger meta data save events, so we get meta data from different // plugins included as we post jQuery(document).trigger('fv_flowplayer_player_meta_save', [data, $tabs]); $tabs.each(function() { var $tab = jQuery(this), is_videos_tab = $tab.hasClass('fv-player-tab-video-files'), is_subtitles_tab = $tab.hasClass('fv-player-tab-subtitles'), $input_groups = ((is_videos_tab || is_subtitles_tab) ? $tab.find('[data-playlist-item]') : $tab.find('input, select, textarea')), save_index = -1; // prepare video and subtitles data, which are duplicated through their input names if (is_videos_tab) { data['videos'] = {}; } else if (is_subtitles_tab) { data['video_meta']['subtitles'] = {}; data['video_meta']['transcript_src'] = {}; } // iterate over all tables in tabs $input_groups.each(function() { // only videos, subtitles tabs have tables, so we only need to search for their inputs when working with those var $inputs = ((is_videos_tab || is_subtitles_tab) ? jQuery(this).find('input, select, textarea').not('.no-data') : jQuery(this).not('.no-data') ), table_index = jQuery(this).data('index'); save_index++; $inputs.each(function() { var $this = jQuery(this), m; while ((m = regex.exec(this.name)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === regex.lastIndex) { regex.lastIndex++; } // let plugins update video meta, if applicable jQuery(document).trigger('fv_flowplayer_video_meta_save', [data, save_index, this]); // videos tab if (is_videos_tab) { if (!data['videos'][save_index]) { data['videos'][save_index] = { id: jQuery('.fv-player-playlist-item[data-index=' + table_index + ']').data('id_video') }; } // check for a meta field if (check_for_video_meta_field(m[1])) { // prepare HLS data, if not prepared yet if (!data['video_meta']['video']) { data['video_meta']['video'] = {}; } if (!data['video_meta']['video'][save_index]) { data['video_meta']['video'][save_index] = {}; } insertUpdateOrDeleteVideoMeta({ data: data, meta_section: 'video', meta_key: get_field_name(m[1]), meta_index: save_index, element: this }); } else { data['videos'][save_index][m[1]] = map_input_value($this); } } // subtitles tab, subtitles inputs else if (is_subtitles_tab) { // TODO: Adding field in PHP with languages => true should add to this array: let video_meta_languages = { subtitles: [], transcript_src: [], } $.each( video_meta_languages, function( k, v ) { if( $this.attr('name') == 'fv_wp_flowplayer_field_' + k ) { if (!data['video_meta'][ k ][save_index]) { data['video_meta'][ k ][save_index] = []; } let parent = $this.closest( '.components-base-control__field' ), lang = parent.find( '[name=fv_wp_flowplayer_field_' + k + '_lang]' ); // jQuery-select the SELECT element when we get an INPUT, since we need to pair them if ( $this[0].nodeName == 'INPUT') { data['video_meta'][ k ][save_index].push({ code : lang.val(), file : $this.val(), id: parent.data('id_videometa') }); } } }); } // all other tabs else { let player_attribute_value = map_input_value($this); if (check_for_player_meta_field(m[1])) { // meta data input insertUpdateOrDeletePlayerMeta({ data: data, meta_section: 'player', meta_key: get_field_name(m[1]), element: this, handle_delete: false }); } else { // Is there a default value for the setting? let field = m[1].replace(/fv_wp_flowplayer_field_/, ''); if( typeof(window.fv_player_editor_defaults[field]) != "undefined" ) { // Is it the same? Do not save. if( window.fv_player_editor_defaults[field] == this.checked ) { continue; // Should it be off while default is on? Save literal false } else if( window.fv_player_editor_defaults[field] && !this.checked ) { player_attribute_value = 'false'; } } // ordinary player attribute data[m[1]] = player_attribute_value; } } } }); }); }); // remove any empty videos, i.e. without a source // this is used when loading data from DB to avoid previewing an empty video that's in editor by default if (data['videos']) { var data_videos_new = {}, x = 0; for (var i in data['videos']) { if (data['videos'][i]['src'] || data['videos'][i]['src1'] || !data['videos'][i]['src2']) { // if we should show preview of a single video only, add that video here, // otherwise add all videos here if (!single_video_showing || x == current_video_to_edit) { data_videos_new[x++] = data['videos'][i]; } else { x++; } } } data['videos'] = data_videos_new; } // add player ID and deleted elements for a DB update data['update'] = current_player_db_id; data['deleted_videos'] = deleted_videos.join(','); data['deleted_video_meta'] = deleted_video_meta.join(','); data['deleted_player_meta'] = deleted_player_meta.join(','); return data; } function debug_log( message, details ) { store_debug_log.push( message ); console.log( 'FV Player Editor: '+message); if( details ) { store_debug_log.push( details ); console.log(details); } } function debug_time( time ) { return Math.round( 100 * ( performance.now() - time ) / 1000 ) / 100 + ' seconds'; } /** * Closing the editor * * remove hidden tags from post editor * * updates the wp-admin -> FV Player screen * * sets data for WordPress Heartbeat to unlock the player * * calls editor_init() for editor clean-up */ function editor_close() { // don't close editor if we have errors showing, otherwise we'd just overlay them by an infinite loader if ( has_errors() ) { return; } // remove TinyMCE hidden tags and other similar tags which aids shortcode editing // to prevent opening the same player over and over editor_content = editor_content.replace(fv_wp_flowplayer_re_insert,''); editor_content = editor_content.replace(fv_wp_flowplayer_re_edit,''); editor_content = editor_content.replace(/#fvp_placeholder#/, ''); editor_content = editor_content.replace(/#fvp_codemirror_placeholder#/, ''); set_post_editor_content(editor_content); // this variable needs to be reset here and not in editor_init set_current_video_to_edit( -1 ); // check if code mirror is active and if so, focus on it & restore cursor position if( instance_code_mirror && instance_code_mirror_cursor_last ) { // run after everything else is done setTimeout(function() { instance_code_mirror.focus(); instance_code_mirror.setCursor( instance_code_mirror_cursor_last, 0 ); instance_code_mirror_cursor_last = false; },0); } if ( !is_fv_player_screen(editor_button_clicked) ) { // todo: what it the point of this call being made? // TODO: Perhaps to ensure the temporary strings in editor are removed? //set_post_editor_content( editor_content.replace( fv_wp_flowplayer_re_insert, '' ) ); // trigger update for the FV Player Custom Videos/Meta Box and Gutenberg field for preview refresh purposes var $editor_button_clicked = $(editor_button_clicked), $fv_player_custom_meta_box = $editor_button_clicked.parents('.fv-player-editor-wrapper').find('.fv-player-editor-field'), $fv_player_gutenberg = $editor_button_clicked.parents('.fv-player-gutenberg'); if ( $fv_player_custom_meta_box.length ) { $fv_player_custom_meta_box.trigger('fv_flowplayer_shortcode_insert'); } // Update the Gutenberg fields if( get_current_player_object().videos && get_current_player_object().videos[0] && fv_player_editor.clientId ) { var src = get_current_player_object().videos[0].src, splash = get_current_player_object().videos[0].splash, title = get_current_player_object().videos[0].title, timeline_previews = get_playlist_video_meta_value( 'timeline_previews', 0 ), hlskey = get_playlist_video_meta_value( 'hls_hlskey', 0 ); 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 } ); } // Refresh the Elementor Widget preview let elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ); if ( elementor_field.length ) { elementor_field.trigger( 'input' ); } } else if( current_player_db_id > 0 ) { // Append or update player row in wp-admin -> FV Player if( fv_player_editor_conf.is_fv_player_screen ) { var playerRow = $('#the-list span[data-player_id="' + current_player_db_id + '"]') if( playerRow.length == 0 ) { var firstRow = $('#the-list tr:first'), newRow = firstRow.clone(); newRow.find('td').html(''); playerRow = newRow.find('td').eq(0); firstRow.before( newRow ) } jQuery.post( ajaxurl, { action : 'fv_player_table_new_row', nonce : fv_player_editor_conf.table_new_row_nonce, playerID : current_player_db_id }, function(response) { playerRow.closest('tr').replaceWith( $(response).find('#the-list tr') ); }); playerRow.append(' 
     
    '); // Update the player on the wp-admin -> Posts, Pages or CPT screen } else if( fv_player_editor_conf.is_edit_posts_screen ) { // Existing player let target_el = jQuery('.fv-player-edit[data-player_id='+current_player_db_id+']'), args = { action : 'fv_player_edit_posts_cell', nonce : fv_player_editor_conf.edit_posts_cell_nonce, playerID : current_player_db_id }; // New player, load from the new post meta if ( target_el.length === 0 && current_post_id ) { target_el = jQuery('.fv-player-edit[data-post-id='+current_post_id+']'); args.post_id = current_post_id; args.meta_key = current_post_meta_key; } target_el.find('.fv_player_splash_list_preview').append('
     
    '); jQuery.post( ajaxurl, args, function(response) { if ( response ) { target_el.replaceWith( response ); } }); } } // we need to do this now to make sure Heartbeat gets the correct data if (current_player_db_id > 0 ){ edit_lock_removal[current_player_db_id] = 1; current_player_db_id = 0; debug_log( 'editor_close current_player_db_id = 0' ); } if ( fv_player_editor_conf.on_close ) { fv_player_editor_conf.on_close(); } editor_init(); } /** * removes previous values from editor * fills new values from shortcode * * @param {int} selected_player_id Optional, force load of specified player ID using "Pick existing player" */ function editor_open( selected_player_id ) { if( !selected_player_id ) { editor_init(); } instance_code_mirror_cursor_last = false; // remove any DB data IDs that may be left in the form $el_editor.find('[data-id]').removeData('id').removeAttr('data-id'); $el_editor.find('[data-id_video]').removeData('id_video').removeAttr('data-id_video'); $el_editor.find('[data-id_videometa]').removeData('id_videometa').removeAttr('data-subtitles'); // fire up editor reset event, so plugins can clear up their data IDs as well $doc.trigger('fv_flowplayer_player_editor_reset'); // reset content of any input fields, except what has .extra-field $el_editor.find("input:not(.extra-field), textarea").each( function() { $(this) .val( '' ) .prop( 'checked', false ) .trigger( 'change' ); } ); $el_editor.find('select:not([multiple])').prop('selectedIndex',0); // select first index, ignore multiselect - let it be handled separately $el_editor.find(".fv_player_field_insert-button").text( 'Insert' ); if( window.fv_player_editor_defaults ) { jQuery.each( window.fv_player_editor_defaults, function(k,v) { var checkbox = $el_editor.find('[name=fv_wp_flowplayer_field_'+k+'][type=checkbox]'); if( checkbox.length ) { checkbox.prop( 'checked', !!v ).trigger( 'change' ); var wrap = checkbox.closest('.components-form-toggle'); wrap.toggleClass( 'is-checked is-default', !!v ); } } ); } var field = $(editor_button_clicked).parents('.fv-player-editor-wrapper, .fv-player-gutenberg').find('.fv-player-editor-field'), clientId = $(editor_button_clicked).parents('.fv-player-editor-wrapper, .fv-player-gutenberg').find('.fv-player-gutenberg-client-id').val(), elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ), is_elementor = elementor_field.length, is_gutenberg = $(editor_button_clicked).parents('.fv-player-gutenberg').length, shortcode = false, shortcode_parse_fix = false; if( clientId ) { fv_player_editor.clientId = clientId; } else { fv_player_editor.clientId = null; } // if we've got a numeric DB ID passed to this function, use it directly // but don't replace editor_content, since we'll need that to be actually updated // rather then set to a player ID if (selected_player_id) { debug_log('Loading for player id: '+selected_player_id ); shortcode = 'fvplayer id="' + selected_player_id + '"'; current_post_id = $(editor_button_clicked).data('post-id'); current_post_meta_key = $(editor_button_clicked).data('meta_key'); if( current_post_id || current_post_meta_key ) { debug_log( 'New player for post #' + current_post_id + ' for ' + current_post_meta_key + ' meta_key.' ); } } else { /** * Load shortcode for the active post editor */ if( fv_player_editor_conf.field_selector ){ let custom_field_selector = jQuery(fv_player_editor_conf.field_selector) // If the pre-configured field was not failed it's a big deal! if( !custom_field_selector.length ){ alert( 'FV Player Editor: Field '+fv_player_editor_conf.field_selector+' not found!' ); } editor_content = custom_field_selector.val(); shortcode = editor_content; // Edit button on wp-admin -> FV Player screen } else if (is_fv_player_screen_edit(editor_button_clicked)) { current_player_db_id = $(editor_button_clicked).data('player_id'); current_post_id = $(editor_button_clicked).data('post-id'); current_post_meta_key = $(editor_button_clicked).data('meta_key'); if( current_post_id || current_post_meta_key ) { debug_log( 'New player for post #' + current_post_id + ' for ' + current_post_meta_key + ' meta_key.' ); editor_content = ''; } else { debug_log('Loading for FV Player screen, player id: '+current_player_db_id ); // create an artificial shortcode from which we can extract the actual player ID later below editor_content = '[fvplayer id="' + current_player_db_id + '"]'; shortcode = editor_content; } } // Add new button on wp-admin -> FV Player screen else if (is_fv_player_screen_add_new(editor_button_clicked)) { debug_log('Loading for FV Player screen, new player' ); // create empty shortcode for Add New button on the list page editor_content = ''; shortcode = ''; } // custom Field or Widget else if (field.length || jQuery('#widget-widget_fvplayer-' + widget_id + '-text').length) { debug_log('Loading for custom field or a widget...'); // this is a horrible hack as it adds the hidden marker to the otherwise clean text field value // just to make sure the shortcode varible below is parsed properly. // But it allows some extra text to be entered into the text widget, so for now - ok if (editor_content.match(/\[/)) { editor_content = '[<' + helper_tag +' rel="FCKFVWPFlowplayerPlaceholder">­' + editor_content.replace('[', '') + ''; } else { editor_content = '<' + helper_tag + ' rel="FCKFVWPFlowplayerPlaceholder">­' + editor_content + ''; } } else if ( is_elementor ) { debug_log( 'Loading for Elementor Widget...', editor_content ); // CodeMirror } else if( instance_code_mirror ) { debug_log('Loading for CodeMirror...' ); // get content editor_content = instance_code_mirror.getDoc().getValue(); // get cursor position var position = instance_code_mirror.getDoc().getCursor(), line = position.line, ch = position.ch, line_value = instance_code_mirror.getDoc().getLine(line); // save cursor position instance_code_mirror_cursor_last = {line: line, ch: ch}; // look for start of shortcode var shotcode_pattern = new RegExp(/\[fvplayer[^\[\]]*]?/g); if( shotcode_pattern.test(line_value) ) { for( var start = ch; start--; start >= 0 ) { if( line_value[start] == '[' ) { var sliced_content = line_value.slice(start); var matched = sliced_content.match(shotcode_pattern); if( matched ) { shortcode = matched[0]; } break; } else if( line_value[start] == ']' ) { break } } } else { // add placeholder for new editor instance_code_mirror.getDoc().replaceRange('#fvp_codemirror_placeholder#', {line: line, ch: ch}, {line: line, ch: ch}); editor_content = instance_code_mirror.getDoc().getValue(); } } // TinyMCE in Text Mode else if (typeof (FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length) { debug_log('Loading for TinyMCE in Text Mode...'); var position = jQuery('#content:not([aria-hidden=true])').prop('selectionStart'); // look for start of shortcode for (var start = position; start--; start >= 0) { if (editor_content[start] == '[') { var sliced_content = editor_content.slice(start); var matched = sliced_content.match(/^\[fvplayer[^\[\]]*]?/); // found the shortcode! if (matched) { shortcode = matched[0]; } break; } else if (editor_content[start] == ']') { break } } // TODO: It would be better to use #fv_player_editor_{random number}# and remember it for the editing session editor_content = editor_content.slice(0, position) + '#fvp_placeholder#' + editor_content.slice(position); } // Foliopress WYSIWYG else if ( instance_fp_wysiwyg && ( instance_tinymce == undefined || (typeof tinyMCE !== 'undefined' && tinyMCE.activeEditor.isHidden()) ) ) { debug_log('Loading for Foliopress WYSIWYG...' ); editor_content = instance_fp_wysiwyg.GetHTML(); if (editor_content.match(fv_wp_flowplayer_re_insert) == null) { instance_fp_wysiwyg.InsertHtml('<' + fvwpflowplayer_helper_tag + ' rel="FCKFVWPFlowplayerPlaceholder">­'); editor_content = instance_fp_wysiwyg.GetHTML(); } } else if( instance_tinymce ) { debug_log('Loading for TinyMCE in Visual Mode...' ); // TinyMCE in Visual Mode editor_content = instance_tinymce.getContent(); instance_tinymce.settings.validate = false; if (editor_content.match(fv_wp_flowplayer_re_insert) == null) { var tags = ['b', 'span', 'div']; for ( let i in tags) { instance_tinymce.execCommand('mceInsertContent', false, '<' + tags[i] + ' data-mce-bogus="1" rel="FCKFVWPFlowplayerPlaceholder">'); editor_content = instance_tinymce.getContent(); fv_wp_flowplayer_re_edit = new RegExp('\\[f[^\\]]*?<' + tags[i] + '[^>]*?rel="FCKFVWPFlowplayerPlaceholder"[^>]*?>.*?.*?[^\]\\]', "mi"); fv_wp_flowplayer_re_insert = new RegExp('<' + tags[i] + '[^>]*?rel="FCKFVWPFlowplayerPlaceholder"[^>]*?>.*?', "gi"); if (editor_content.match(fv_wp_flowplayer_re_insert)) { break; } } } instance_tinymce.settings.validate = true; } else { editor_content = ''; } if( !shortcode ){ let content = editor_content.replace(/\n/g, '\uffff'); if ( is_elementor || is_gutenberg ) { shortcode = content; } else { let match = content.match( fv_wp_flowplayer_re_edit ); if( match ) { shortcode = match[0]; } } } } // remove visual editor placeholders etc. if (shortcode) { shortcode = shortcode .replace(/^\[|]+$/gm, '') .replace(fv_wp_flowplayer_re_insert, '') .replace(/\\'/g, '''); } if( shortcode ) { debug_log('Loading shortcode: '+shortcode ); // check for new, DB-based player shortcode var result = /fvplayer.* id="([\d,]+)"/g.exec(shortcode); if (result !== null) { shortcode_parse_fix = shortcode .replace(/(popup|ad)='[^']*?'/g, '') .replace(/(popup|ad)="(.*?[^\\\\/])"/g, ''); shortcode_remains = shortcode_parse_fix.replace( /^\S+\s*?/, '' ); // DB-based player, create a "wait" overlay overlay_show('loading'); // store player ID into fv_player_conf, so we can keep sending it // in WP heartbeat current_player_db_id = result[1]; debug_log('Loading shortcode player id: '+current_player_db_id ); if (edit_lock_removal[result[1]]) { delete edit_lock_removal[result[1]]; } // check if we don't have multiple-playlists shortcode, // in which case we need to stop and show an error message if (shortcode.indexOf(',') > -1) { overlay_show('message', 'Shortcode editor is not available for multiple players shortcode tag.'); return; } // stop Ajax saving that might occur from thinking it's a draft taking place is_unsaved = false; is_loading = true; // now load playlist data // load video data via an AJAX call debug_log('Running fv_player_db_load Ajax.'); let time_start = performance.now(); fv_player_shortcode_editor_ajax = jQuery.post( ajaxurl + '?fv_player_db_load=' + result[1], { action : 'fv_player_db_load', nonce : fv_player_editor_conf.db_load_nonce, playerID : result[1], current_video_to_edit: get_playlist_items_count() > 1 ? current_video_to_edit : -1, }, function(response) { var vids = response['videos']; debug_log('Finished fv_player_db_load Ajax in ' + debug_time( time_start ) + '.',response); if (response) { if ( response.debug_duplicate_players && response.debug_duplicate_players.length ) { alert( "FV Player Editor warning:\n\n\ 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\ Any changes made to the videos will update the other player as well. \ You can also carefully remove the duplicate player by checking which one is actually used in your post.\n\n\ Please also contact FV Player support with the following debug information:\n\n\ " + store_debug_log.join("\n") ); } if( response.error ) { reset_editor_ids(); overlay_show('message', response.error ); // The editor failed to load, it's not locked edit_lock_removal[current_player_db_id] = 1; // Prevent autosave notice from appearing is_unsaved = true; is_loading = false; return; } let time_start = performance.now(); init_saved_player_fields( result[1] ); // remove everything with index 0 and the initial video placeholder, // otherwise our indexing & previews wouldn't work correctly jQuery('[data-index="0"]').remove(); jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').remove(); jQuery('.fv-player-tab-video-files table').remove(); set_embeds(response['embeds']); current_player_object = response; // fire the player load event to cater for any plugins listening $doc.trigger('fv_flowplayer_player_meta_load', [response]); for (var key in response) { // put the field value where it belongs if (key !== 'videos') { // in case of meta data, proceed with each player meta one by one if (key == 'meta') { for ( let i in response[key]) { set_editor_field(response[key][i]['meta_key'], response[key][i]['meta_value'], response[key][i]['id']); } } else { set_editor_field(key, response[key]); } } } let new_video_tabs = [], new_subtitles_tabs = []; // add videos from the DB for (var x in vids) { let video_meta_languages = { subtitles: [], transcript_src: [], }, video_meta_to_set = []; // add all subtitles if (vids[x].meta && vids[x].meta.length) { for (var m in vids[x].meta) { let is_languages_meta = false; $.each( video_meta_languages, function( k, v ) { if (vids[x].meta[m].meta_key.indexOf( k ) > -1) { is_languages_meta = true; // Map subtitles_en to en and subtitles to just empty string let lang = vids[x].meta[m].meta_key.replace( k + '_', ''); if ( 'subtitles' === lang ) { lang = ''; } video_meta_languages[k].push({ lang: lang, file: vids[x].meta[m].meta_value, id: vids[x].meta[m].id }); } } ); if ( ! is_languages_meta ) { video_meta_to_set.push(vids[x].meta[m]); } } } let new_item = playlist_item_add( vids[x], true ); new_video_tabs.push( new_item.video_tab ); new_subtitles_tabs.push( new_item.subtitles_tab ); $.each( video_meta_languages, function( k, v ) { for (var i in v) { language_add( k, v[i].file, v[i].lang, new_item.subtitles_tab, v[i].id); } } ); // This sets some additional video meta fields, like remove_black_bars if (video_meta_to_set.length) { for ( let i in video_meta_to_set) { 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 ); } } // fire up meta load event for this video, so plugins can process it and react $doc.trigger('fv_flowplayer_video_meta_load', [ x, vids[x].meta, new_item.video_tab, new_item.subtitles_tab ] ); } // Add all the new video and subtitle tabs to the DOM all at once jQuery('.fv-player-tab-video-files').append( new_video_tabs ); jQuery('.fv-player-tab-subtitles').append( new_subtitles_tabs ); debug_log( 'Finished populating editor fields for ' + vids.length + ' videos in ' + debug_time( time_start ) + '.' ); // show playlist instead of the "add new video" form // if we have more than 1 video if( current_video_to_edit > -1 ) { playlist_item_show(current_video_to_edit); } else if ( vids.length > 1 || fv_player_editor_conf.frontend ) { playlist_show(); } else { playlist_item_show(0); } // if this player is published, mark it as such has_draft_status = ( response.status == 'draft' ); // Set the current data as previous to let auto-saving detect changes ajax_save_previous = build_ajax_data(true); is_loading = false; } overlay_hide(); if ( response.html ) { // auto-refresh preview el_preview_target.html( response.html ) $doc.trigger('fvp-preview-complete'); } // show the Insert button, as this is only used when adding a new player into a post // and using the Pick existing player button, where we need to be able to actually // insert the player code into the editor // ... also, keep the Pick existing player button showing, if we decided to choose // a different player if ( selected_player_id ) { insert_button_toggle(true); copy_player_button_toggle(true); } else if ( response.status == 'draft' ) { // show Save / Insert button, as we're still // in draft mode for this player insert_button_toggle(true); fix_save_btn_text(); } else { insert_button_toggle(false); copy_player_button_toggle(false); } $doc.trigger('fv_player_editor_player_loaded'); $doc.trigger('fv_player_editor_finished'); }).fail( function( jqXHR, textStatus, errorThrown ) { if ( jqXHR.status == 404 ) { overlay_show('message', 'The requested player #' + result[1] + ' could not be found.'); } else { overlay_show('message', 'An unexpected error has occurred while loading player #' + result[1] + ': ' + errorThrown + '

    Please try again.'); } // show the Insert button, as this is only used when adding a new player into a post // and using the Pick existing player button, where we need to be able to actually // insert the player code into the editor // ... also, keep the Pick existing player button showing, if we decided to choose // a different player if ( selected_player_id ) { insert_button_toggle(true); copy_player_button_toggle(true); } }); } else { debug_log('Loading shortcode without player id...'); // TODO: Check if all the values are set properly, for example controlbar checkbox is not set properly anymore $doc.trigger('fv-player-editor-non-db-shortcode'); // ordinary text shortcode in the editor shortcode_parse_fix = shortcode.replace(/(popup|ad)='[^']*?'/g, ''); shortcode_parse_fix = shortcode_parse_fix.replace(/(popup|ad)="(.*?[^\\\\/])"/g, ''); shortcode_remains = shortcode_parse_fix.replace( /^\S+\s*?/, '' ); var srcurl = shortcode_parse_arg( shortcode_parse_fix, 'src' ); var srcurl1 = shortcode_parse_arg( shortcode, 'src1' ); var srcurl2 = shortcode_parse_arg( shortcode, 'src2' ); var srcrtmp = shortcode_parse_arg( shortcode, 'rtmp' ); var srcrtmp_path = shortcode_parse_arg( shortcode, 'rtmp_path' ); var iwidth = shortcode_parse_arg( shortcode_parse_fix, 'width' ); var iheight = shortcode_parse_arg( shortcode_parse_fix, 'height' ); var sad_skip = shortcode_parse_arg( shortcode, 'ad_skip' ); var scontrolbar = shortcode_parse_arg( shortcode, 'controlbar' ); var sautoplay = shortcode_parse_arg( shortcode, 'autoplay' ); var sliststyle = shortcode_parse_arg( shortcode, 'liststyle' ); var sembed = shortcode_parse_arg( shortcode_parse_fix, 'embed' ); var sloop = shortcode_parse_arg( shortcode, 'loop' ); var slive = shortcode_parse_arg( shortcode, 'live' ); var sshare = shortcode_parse_arg( shortcode, 'share', false, shortcode_share_parse_arg ); var sspeed = shortcode_parse_arg( shortcode, 'speed' ); var ssplash = shortcode_parse_arg( shortcode, 'splash' ); var ssplashend = shortcode_parse_arg( shortcode, 'splashend' ); var ssticky = shortcode_parse_arg( shortcode, 'sticky' ); var splaylist_advance = shortcode_parse_arg( shortcode, 'playlist_advance' ); var ssubtitles = shortcode_parse_arg( shortcode, 'subtitles' ); var aSubtitlesLangs = shortcode.match(/subtitles_[a-z][a-z]+/g); for( let i in aSubtitlesLangs ){ // move shortcode_parse_arg( shortcode, aSubtitlesLangs[i], false, shortcode_subtitle_parse_arg ); } if(!aSubtitlesLangs){ // move subtitle_language_add(false, false ); } var smobile = shortcode_parse_arg( shortcode, 'mobile' ); var sredirect = shortcode_parse_arg( shortcode, 'redirect' ); var sCaptions = shortcode_parse_arg( shortcode, 'caption' ); var sSplashText = shortcode_parse_arg( shortcode, 'splash_text' ); var sPlaylist = shortcode_parse_arg( shortcode, 'playlist' ); var sad = shortcode_parse_arg( shortcode, 'ad', true ); var iadwidth = shortcode_parse_arg( shortcode, 'ad_width' ); var iadheight = shortcode_parse_arg( shortcode, 'ad_height' ); // TODO: Test if RTMP is shown if( srcrtmp != null && srcrtmp[1] != null ) { jQuery(".fv_wp_flowplayer_field_rtmp").val( srcrtmp[1] ); jQuery(".fv_wp_flowplayer_field_rtmp_wrapper").show(); } if( srcrtmp_path != null && srcrtmp_path[1] != null ) { jQuery(".fv_wp_flowplayer_field_rtmp_path").val( srcrtmp_path[1] ); jQuery(".fv_wp_flowplayer_field_rtmp_wrapper").show(); } var playlist_row = jQuery('.fv-player-tab-playlist tbody tr:first') if( srcurl != null && srcurl[1] != null ) { get_field( "src" ).val( srcurl[1] ).trigger('change'); } if( srcurl1 != null && srcurl1[1] != null ) { get_field( "src1" ).val( srcurl1[1] ).trigger('change'); if( srcurl2 != null && srcurl2[1] != null ) { get_field( "src2" ).val( srcurl2[1] ).trigger('change'); } get_field( 'toggle_advanced_settings' ).prop( 'checked', true ).trigger('change'); } if( srcurl != null && srcurl[1] != null ) { get_field( "src" ).val( srcurl[1] ); playlist_row.find('.fvp_item_video-filename').text( srcurl[1] ); } get_field('width').val(iwidth[1] || ''); get_field('height').val(iheight[1] || ''); if( sautoplay != null && sautoplay[1] != null ) { if (sautoplay[1] == 'true') get_field("autoplay")[0].selectedIndex = 1; if (sautoplay[1] == 'false') get_field("autoplay")[0].selectedIndex = 2; if (sautoplay[1] == 'muted') get_field("autoplay")[0].selectedIndex = 3; } if( sliststyle != null && sliststyle[1] != null ) { var objPlaylistStyle = get_field("playlist")[0]; if (sliststyle[1] == 'horizontal') objPlaylistStyle.selectedIndex = 1; if (sliststyle[1] == 'tabs') objPlaylistStyle.selectedIndex = 2; if (sliststyle[1] == 'prevnext') objPlaylistStyle.selectedIndex = 3; if (sliststyle[1] == 'vertical') objPlaylistStyle.selectedIndex = 4; if (sliststyle[1] == 'slider') objPlaylistStyle.selectedIndex = 5; if (sliststyle[1] == 'season') objPlaylistStyle.selectedIndex = 6; if (sliststyle[1] == 'polaroid') objPlaylistStyle.selectedIndex = 7; if (sliststyle[1] == 'text') objPlaylistStyle.selectedIndex = 8; } if( sembed != null && sembed[1] != null ) { if (sembed[1] == 'true') get_field("embed")[0].selectedIndex = 1; if (sembed[1] == 'false') get_field("embed")[0].selectedIndex = 2; } if( smobile != null && smobile[1] != null ) get_field("mobile").val(smobile[1]); if( ssplash != null && ssplash[1] != null ) { get_field("splash").val( ssplash[1] ).trigger('change'); var playlist_img = jQuery('') .attr('width', 120 ) .attr('src', ssplash[1] ); playlist_row.find('.fvp_item_splash').html( playlist_img ); } var aSubtitles = false; if( ssubtitles != null && ssubtitles[1] != null ) { aSubtitles = ssubtitles[1].split(';'); get_field("subtitles").eq(0).val( aSubtitles[0] ); aSubtitles.shift(); // the first item is no longer needed for playlist parsing which will follow } if( ssticky != null && ssticky[1] != null ) { if (ssticky[1] == 'true') get_field("sticky")[0].selectedIndex = 1; if (ssticky[1] == 'false') get_field("sticky")[0].selectedIndex = 2; } if( sad != null && sad[1] != null ) { sad = sad[1].replace(/'/g,'\'').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>'); sad = sad.replace(/&/g,'&'); get_field("overlay").val(sad); get_field( 'toggle_overlay' ).prop('checked', true).trigger('change'); } if( iadheight != null && iadheight[1] != null ) get_field("overlay_height").val(iadheight[1]); if( iadwidth != null && iadwidth[1] != null ) get_field("overlay_width").val(iadwidth[1]); if( sad_skip != null && sad_skip[1] != null && sad_skip[1] == 'yes' ) get_field("overlay_skip").prop('checked',true).trigger('change'); if( sspeed != null && sspeed[1] != null ) { if (sspeed[1] == 'buttons') get_field("speed")[0].selectedIndex = 1; if (sspeed[1] == 'no') get_field("speed")[0].selectedIndex = 2; } /* if( ssplashend != null && ssplashend[1] != null && ssplashend[1] == 'show' ) document.getElementById("fv_wp_flowplayer_field_splashend").checked = 1; if( sloop != null && sloop[1] != null && sloop[1] == 'true' ) document.getElementById("fv_wp_flowplayer_field_loop").checked = 1; if( sredirect != null && sredirect[1] != null ) document.getElementById("fv_wp_flowplayer_field_redirect").value = sredirect[1]; */ if( sSplashText != null && sSplashText[1] != null ) { get_field("splash_text").val(sSplashText[1]); } /* * Video end dropdown */ //TODO: shortcode popup // get_field("popup")[0].parentNode.style.display = 'none' var spopup = shortcode_parse_arg( shortcode, 'popup', true ); let end_actions_val = false; if( sredirect != null && sredirect[1] != null ){ end_actions_val = 2; get_field("redirect").val( sredirect[1] ); } else if( sloop != null && sloop[1] != null && sloop[1] == 'true' ){ end_actions_val = 3; } else if( spopup != null && spopup[1] != null ) { end_actions_val = 4; spopup = spopup[1].replace(/'/g,'\'').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>'); spopup = spopup.replace(/&/g,'&'); if( spopup.match(/email-[0-9]*/)){ end_actions_val = 6; get_field("email_list").val(spopup.match(/email-([0-9]*)/)[1]); } else { get_field("popup_id").val(spopup); } } else if( ssplashend != null && ssplashend[1] != null && ssplashend[1] == 'show' ){ end_actions_val = 5; } if( end_actions_val ) { get_field("end_actions").prop( 'selectedIndex', end_actions_val ).trigger('change'); get_field( 'toggle_end_action' ).prop('checked', true).trigger('change'); } if( splaylist_advance != null && splaylist_advance[1] != null ) { let field = get_field("playlist_advance")[0]; if (splaylist_advance[1] == 'true') field.selectedIndex = 1; if (splaylist_advance[1] == 'false') field.selectedIndex = 2; } if( scontrolbar != null && scontrolbar[1] != null ) { let field = get_field("controlbar")[0]; if (scontrolbar[1] == 'yes' || scontrolbar[1] == 'show' ) field.selectedIndex = 1; if (scontrolbar[1] == 'no' || scontrolbar[1] == 'hide' ) field.selectedIndex = 2; } var aCaptions = false; if( sCaptions ) { aCaptions = shortcode_arg_split(sCaptions); var caption = aCaptions.shift(); get_field("title").val( caption ); if( caption ) { playlist_row.find('.fvp_item_video-filename').text( caption ); } } var aSplashText = false; if( sSplashText ) { aSplashText = shortcode_arg_split(sSplashText); var splash_text = aSplashText.shift(); get_field("splash_text").val( splash_text ); } if( sPlaylist ) { // check for all-numeric playlist items separated by commas // which outlines video IDs from a database var aPlaylist = sPlaylist[1].split(';'); for ( let i in aPlaylist) { // TODO: Will this work? playlist_item_add( { src: aPlaylist[i], title: aCaptions[i], subtitles: aSubtitles[i], splash_text: aSplashText[i] } ); } } if( jQuery('.fv-fp-subtitles .fv-fp-subtitle:first input.fv_wp_flowplayer_field_subtitles').val() == '' ) { jQuery('.fv-fp-subtitles .fv-fp-subtitle:first').remove(); } try { jQuery(document).trigger('fv_flowplayer_shortcode_parse', [ shortcode_parse_fix, shortcode_remains ] ); } catch(e) { debug_log('Error: fv_flowplayer_shortcode_parse', e); } if (slive != null && slive[1] != null && slive[1] == 'true') { jQuery("input[name=fv_wp_flowplayer_field_live]").each(function () { this.checked = 1; jQuery(this).closest('tr').show(); }); } if(sPlaylist){ playlist_show(); } else { playlist_item_show(0); } tabs_refresh(); } store_shortcode_args = {}; always_keep_shortcode_args = {}; for( let i in fv_player_editor_conf.shortcode_args_to_preserve ) { let value = shortcode_parse_arg( shortcode_parse_fix, fv_player_editor_conf.shortcode_args_to_preserve[i] ); if (value && value[1]) { store_shortcode_args[fv_player_editor_conf.shortcode_args_to_preserve[i]] = value[1]; } } if( store_shortcode_args.length ) { debug_log('Preserving shortcode args', store_shortcode_args ); } for( let i in fv_player_editor_conf.shortcode_args_not_db_compatible ) { let value = shortcode_parse_arg( shortcode_parse_fix, fv_player_editor_conf.shortcode_args_not_db_compatible[i] ); if (value && value[1]) { always_keep_shortcode_args[fv_player_editor_conf.shortcode_args_not_db_compatible[i]] = value[1]; } } if( always_keep_shortcode_args.length ) { debug_log('Always preserve shortcode args', always_keep_shortcode_args ); } } else { debug_log('New player...' ); playlist_item_show(0); jQuery(document).trigger('fv_flowplayer_shortcode_new'); shortcode_remains = ''; fix_save_btn_text(); } $doc.trigger('fv_player_editor_finished'); } /** * Makes sure that the Save / Insert button is showing the correct text * based on current context. */ function fix_save_btn_text() { // rename insert to save for new playlists if we come from list view if ( is_fv_player_screen_add_new( editor_button_clicked ) || is_fv_player_screen_edit( editor_button_clicked ) ) { jQuery('.fv_player_field_insert-button').text('Save'); } else { // we're in a post / widget, rename save button to Insert jQuery('.fv_player_field_insert-button').text('Insert'); } } /* * Saving the data */ function editor_submit() { // bail out if we're already saving, we're loading meta data or we have errors if ( ajax_save_this_please || is_saving || is_loading_meta_data || has_errors() ) { // if we're saving a new player, let's disable the Save button and wait until meta data are loaded if ( current_player_db_id < 0 ) { if (is_loading_meta_data) { insert_button_toggle_disabled(true); $el_editor.find('.button-primary').text('Saving...'); var checker = setInterval(function() { if (is_loading_meta_data <= 0) { clearInterval(checker); editor_submit(); // call this function again, so we can really save this time } }, 500); return; } } else { // if we're updating a player, just return here if we're loading meta data, // as that would result in duplicate save - once with and once without meta data if (is_loading_meta_data) { return; } } // we have errors, disable the Save button if ( has_errors() ) { insert_button_toggle_disabled(true); return; } } // TODO: Tell people they only have RTMP var field_rtmp = get_field("rtmp"), field_rtmp_path = get_field("rtmp_path") if( field_rtmp.attr('placeholder') == '' && get_field("rtmp_wrapper").is(":visible") && ( ( field_rtmp.val() != '' && field_rtmp_path.val() == '' ) || ( field_rtmp.val() == '' && field_rtmp_path.val() != '' ) ) ) { alert('Please enter both server and path for your RTMP video.'); return false; } else if( get_field("src").val() == '' && get_field("rtmp").val() == '' && get_field("rtmp_path").val() == '') { alert('Please enter the file name of your video file.'); return false; } var ajax_data = build_ajax_data(true); overlay_show('loading'); var player_was_non_db = (current_player_db_id > -1 ? false : true); // unmark DB player ID as being currently edited if ( current_player_db_id > 0 ) { current_player_db_id = 0; } // if player should be in published state, add it into the AJAX data if ( !has_draft_status ) { ajax_data['status'] = 'published'; } debug_log('Running fv_player_db_save Ajax on submit.'); ajax_data = set_control_fields( ajax_data ); let time_start = performance.now(); // save data // We use ?fv_player_db_save=1 as some people use that to exclude firewall rules $.ajax({ type: 'POST', url: ajaxurl + '?action=fv_player_db_save', data: JSON.stringify( { data: ajax_data, nonce: fv_player_editor_conf.edit_nonce, } ), contentType: 'application/json', dataType: 'json', success: function(response) { debug_log( 'Finished fv_player_db_save Ajax in ' + debug_time( time_start ) + '.' ); if( response.error ) { if( response.error && response.fatal_error ) { debug_log( 'Fatal error in fv_player_db_save: ' + response.fatal_error ); let json_export_data = jQuery('
    ').text(JSON.stringify(ajax_data)).html(); let overlay = overlay_show('error_saving'); overlay.find('textarea').val( response.error + '\n\nJSON data:\n\n' + $('
    ').text(json_export_data).html() ); jQuery('#fv_player_copy_to_clipboard').select(); } else { add_notice( 'error', response.error ); debug_log( 'Error in fv_player_db_save: ' + response.error ); } el_spinner.hide(); is_saving = false; return; } // player saved, reset draft status is_unsaved = false; //is_draft_changed = false; current_player_db_id = parseInt(response.id); if( current_player_db_id > 0 ) { let shortcode_insert = '[fvplayer id="' + current_player_db_id + '"'; // we have extra presentation parameters to keep if (store_shortcode_args) { // if this was a non-DB player and is being converted into a DB-player, // remove all parameters to keep that will go into the DB var args_to_keep = {}; for (var i in store_shortcode_args) { if (always_keep_shortcode_args[ i ]) { args_to_keep[ i ] = store_shortcode_args[ i ]; } } store_shortcode_args = args_to_keep; shortcode_insert += add_shortcode_args( store_shortcode_args ); } else if (always_keep_shortcode_args && player_was_non_db) { // we have extra parameters to keep that are DB-incompatible shortcode_insert += add_shortcode_args( always_keep_shortcode_args ); } else { // simple DB shortcode, no extra presentation parameters insert_shortcode('[fvplayer id="' + current_player_db_id + '"]'); } shortcode_insert += ']'; insert_shortcode( shortcode_insert ); if( fv_player_editor.clientId ) { wp.data.dispatch( 'core/block-editor' ).updateBlockAttributes(fv_player_editor.clientId, { shortcodeContent: shortcode_insert, player_id: current_player_db_id }); } jQuery(".fv-wordpress-flowplayer-button").fv_player_box.close(); } else { let json_export_data = jQuery('
    ').text(JSON.stringify(ajax_data)).html(); let overlay = overlay_show('error_saving'); overlay.find('textarea').val( $('
    ').text(json_export_data).html() ); jQuery('#fv_player_copy_to_clipboard').select(); } }, error: function( jqXHR, textStatus, errorThrown ) { overlay_show('message', 'An unexpected error has occurred while saving player: ' + errorThrown + '

    Please try again'); debug_log( 'HTTP Error in fv_player_db_save: ' + errorThrown + ': ' + jqXHR.responseText ); } }); return; } function fv_wp_flowplayer_dialog_resize() { debug_log('Deprecated fv_wp_flowplayer_dialog_resize() call.'); } function get_pretty_aspect_ratio( video ) { let w, h, dividend, divisor, remainder; // Sanitize input video.width = parseInt( video.width ); video.height = parseInt( video.height ); video.aspect_ratio = parseFloat( video.aspect_ratio ); if( video.width && video.height ) { w = video.width; h = video.height; } else if( video.aspect_ratio ) { w = 1; h = video.aspect_ratio; } else { return false; } if( h == w ) { return '1:1'; } else { if( h > w ) { dividend = h; divisor = w; } if( w > h ) { dividend = w; divisor = h; } let gcd = -1, loop_runs = 0; while( gcd == -1 ) { loop_runs++; // Avoid endless loop, what if... if( loop_runs > 100 ) { gcd = divisor; break; } remainder = dividend % divisor; if( remainder == 0 ){ gcd = divisor; } else { dividend = divisor; divisor = remainder; } } return ( w / gcd ) + ':' + ( h / gcd ); } } function insert_button_toggle( show ) { // Do not show Insert button if player is configured for a set field if( show && fv_player_editor_conf.field_selector ) { return; } $('.fv_player_field_insert-button').toggle( show ); } function insert_button_toggle_disabled( disable ) { var button = $('.fv_player_field_insert-button'); if( disable ) { button.prop('disabled', true); } else { button.prop('disabled', false); } } /** * Sends new shortcode to post editor */ function insert_shortcode( shortcode ) { // do not insert new shortcode if using button on wp-admin -> FV Player if( is_fv_player_screen(editor_button_clicked) ) { return; } var field = $(editor_button_clicked).parents('.fv-player-editor-wrapper').find('.fv-player-editor-field'), elementor_field = $(editor_button_clicked).closest( '#elementor-controls' ).find( '[data-setting="shortcode"]' ), gutenberg = $(editor_button_clicked).parents('.fv-player-gutenberg').find('.fv-player-editor-field'), widget = jQuery('#widget-widget_fvplayer-'+widget_id+'-text'), custom_field_selector = jQuery(fv_player_editor_conf.field_selector); if ( fv_player_editor_conf.on_insert ) { fv_player_editor_conf.on_insert( shortcode ); return; } // Field set by the [fvplayer_editor field="{selector}"] if( custom_field_selector.length ){ custom_field_selector.val( shortcode ); // is there a Gutenberg field together in wrapper with the button? } else if( gutenberg.length ) { // is there a plain text field together in wrapper with the button? } else if (field.length) { field.val(shortcode); // Prevents double event triggering in FV Player Custom Video box //field.trigger('fv_flowplayer_shortcode_insert', [shortcode]); } else if ( elementor_field.length ) { elementor_field.val( shortcode ).trigger( 'input' ); // FV Player in a Widget } else if( widget.length ){ widget.val( shortcode ); widget.trigger('keyup'); // trigger keyup to make sure Elementor updates the content widget.trigger('fv_flowplayer_shortcode_insert', [ shortcode ] ); // CodeMirror tab } else if(instance_code_mirror) { editor_content = editor_content.replace(/#fvp_codemirror_placeholder#/, shortcode); set_post_editor_content(editor_content); } else if (typeof(FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length) { // editing if( editor_content.match(/\[.*?#fvp_placeholder#.*?\]/) ) { editor_content = editor_content.replace(/\[.*?#fvp_placeholder#.*?\]/, shortcode); //inserting } else { editor_content = editor_content.replace(/#fvp_placeholder#/, shortcode); } set_post_editor_content(editor_content); // or are we editing a shortcode in post content? } else if (editor_content.match(fv_wp_flowplayer_re_edit)) { editor_content = editor_content.replace(fv_wp_flowplayer_re_edit, shortcode) set_post_editor_content(editor_content); // is it a new player instance } else { // in existing post content? if (editor_content != '') { editor_content = editor_content.replace(fv_wp_flowplayer_re_insert, shortcode) set_post_editor_content(editor_content); // in blank post? } else { editor_content = shortcode; send_to_editor(shortcode); } } } /* Determines if the button clicked is on wp-admin -> FV Player or wp-admin -> Posts (if using FV Player Video Custom Fields) */ function is_fv_player_screen(button) { // TODO: Should use fv_player_editor_conf.is_fv_player_screen || fv_player_editor_conf.is_edit_posts_screen return is_fv_player_screen_add_new(button) || is_fv_player_screen_edit(button); } /* Determines if the button clicked is Add New on wp-admin -> FV Player */ function is_fv_player_screen_add_new(button) { return typeof( $(button).data('add_new') ) != 'undefined'; } /* Determines if the button clicked is Edit on wp-admin -> FV Player */ function is_fv_player_screen_edit(button) { return ( typeof( $(button).data('player_id') ) != 'undefined' || typeof( $(button).data('post-id') ) != 'undefined' ); } /* Determines if the button clicked is Insert/Edit on wp-admin -> Appearance -> Widgets */ function is_fv_player_widgets(button) { return button.id.indexOf('widget-widget_fvplayer') > -1; } function is_live_stream( video ) { return !! ( video.src && video.src.match( /\.m3u8|live/ ) ); } /* Sets lightbox class once it opens */ function lightbox_open() { $("#fv_player_box").addClass("fv-flowplayer-shortcode-editor"); } function map_checkbox_value( $element ) { var input_type = $element.get(0).type.toLowerCase(), is_checked = $element.get(0).checked; if ( 'checkbox' == input_type ) { return is_checked ? 'true' : ''; } } function map_dropdown_value( dropdown_element ) { var $valueLessOptions = dropdown_element.find('option:not([value])'), value = dropdown_element[0].value.toLowerCase(), index = dropdown_element[0].selectedIndex; // multiselect element if(dropdown_element[0].multiple) { var selected = [], options = dropdown_element[0].options, opt; for (var i=0, iLen=options.length; i 1){ $('.fv-player-playlist-item-title').html('Playlist item no. ' + ++new_index); $('.playlist_edit').html($('.playlist_edit').data('edit')); $el_editor.addClass( 'is-playlist' ); }else{ $('.playlist_edit').html($('.playlist_edit').data('create')); $el_editor.addClass( 'is-singular' ); } // As Flowplayer only lets us set RTMP server for the first video in playlist, prefill it for this new item as well if(new_index > 1){ get_field('rtmp',video_tab).val( get_field('rtmp',$('.fv-player-tab-video-files table').eq(0) ).val()).attr('readonly',true); } $('.fv_wp_flowplayer_field_subtitles_lang, .subtitle_language_add_link').attr('style',false); tabs_refresh(); if( previous_index != current_video_to_edit && get_playlist_items_count() > 1 ) { reload_preview( current_video_to_edit ); } hide_inputs(); $doc.trigger('fv-player-editor-video-opened', [ new_index ] ); /** * Upgrade text inputs to select boxes for matching fields */ window.fv_player_editor_fields_with_language.forEach( function( field_name ) { let fields_with_languages = $( '.fv_wp_flowplayer_field_' + field_name + '_lang', subtitles_tab ); fields_with_languages.each( function() { let $element = $( this ), value = $element.val(); $element.replaceWith( '' ); } ); } ); } /** * Recalculate the data-index values for playlist items */ function playlist_index() { $doc.trigger('fv-player-editor-initial-indexing'); $('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').each( index ); $('.fv-player-tab-video-files [data-playlist-item]').each( index ); $('.fv-player-tab.fv-player-tab-subtitles [data-playlist-item]').each( index ); function index() { /** * The element already had the data-index defined before, so it seems * we need to also set it with data(), since jQuery caches the first * .data() retrieval in the internal data object. * * We cannot just use .data() as we use CSS selectors for elements in JS. */ $(this).attr('data-index', $(this).index() ).data( 'index', $(this).index() ); } } // fills playlist editor table from individual video items function playlist_refresh() { // fills playlist editor table from individual video items let video_files = jQuery('.fv-player-tab-video-files [data-playlist-item]'); video_files.each( function( k, v ) { let current = jQuery(v), video = get_playlist_video_object( k ), currentUrl = get_field("src",current).val(); if (!currentUrl.length) { currentUrl = 'Video ' + (k + 1); } let playlist_row = jQuery('.fv-player-tab-playlist #fv-player-editor-playlist .fv-player-editor-playlist-item').eq( current.data('index') ); let video_preview = video.splash_display ? video.splash_display : get_field("splash", current).val(), playlist_img = jQuery('') .attr('width', 120 ) .attr('src', video_preview ); playlist_row.find('.fvp_item_video-thumbnail') .html( video_preview.length ? playlist_img : '' ) .toggleClass( 'no-img', !video_preview.length ); let video_name = decodeURIComponent(currentUrl).split("/").pop(); video_name = video_name.replace(/\+/g,' '); video_name = video_name.replace(/watch\?v=/,'YouTube: '); let playlist_title = playlist_row.find('.fvp_item_video-filename'); playlist_title.text( video_name ); // do not put in title if it's loading if (!playlist_title.hasClass('fv-player-shortcode-editor-small-spinner')) { let title = get_field("title",current).val(); if( title ) { playlist_title.text( title ); } } playlist_row.find('.fvp_item_video-duration').text( seconds_to_hms( video.duration ) ); }); } /** * Displays playlist editor * keywords: show playlist */ function playlist_show() { current_video_db_id = 0; editing_video_details = false; $el_editor .addClass( 'is-playlist-active' ) .removeClass( 'is-playlist' ) .removeClass( 'is-singular' ) .removeClass( 'is-singular-active' ); // show all the tabs previously hidden jQuery('.fv-player-tabs-header .nav-tab').attr('style',false); jQuery('a[data-tab=fv-player-tab-playlist]').trigger('click'); set_current_video_to_edit( -1 ); playlist_index(); playlist_refresh(); playlist_index(); jQuery('.fv-player-tab-playlist').show(); tabs_refresh(); show_end_of_video_actions(); $doc.trigger('fv-player-editor-video-opened', [ current_video_to_edit ] ); return false; } function reload_preview( video_index ) { if( video_index > -1 && ( !current_player_object.videos || !current_player_object.videos[video_index] ) || video_index== -1 && !current_player_object.videos ) { reset_preview(); return; } el_spinner.show(); debug_log('Running fv_player_db_load Ajax for preview.'); let time_start = performance.now(); // load player data and reload preview of the full player // when we go back from editing a single video in a playlist fv_player_shortcode_editor_ajax = jQuery.post( ajaxurl + '?fv_player_db_load=' + current_player_db_id, { action : 'fv_player_db_load', nonce : fv_player_editor_conf.db_load_nonce, playerID : current_player_db_id, current_video_to_edit: get_playlist_items_count() > 1 ? video_index : -1, }, function(response) { debug_log('Finished fv_player_db_load Ajax in ' + debug_time( time_start ) + '.',response); if ( response.html ) { // auto-refresh preview el_preview_target.html( response.html ) $doc.trigger('fvp-preview-complete'); } }).fail(function( jqXHR, textStatus, errorThrown ) { if ( jqXHR.status == 404) { overlay_show('message', 'The requested player #' + current_player_db_id + ' could not be found.'); } else { overlay_show('message', 'An unexpected error has occurred while loading player #' + current_player_db_id + ': ' + errorThrown + '

    Please try again.'); } }).always(function() { el_spinner.hide(); }); } function remove_notices() { $el_notices.html(''); } function reset_editor_ids() { debug_log( 'reset_editor_ids' ); current_player_db_id = 0; current_player_object = false; current_video_db_id = 0; current_post_id = 0; current_post_meta_key = 0; } function reset_preview() { $el_preview.attr('class','preview-no'); if( fv_player_editor_conf.frontend ) { $el_editor.addClass('is-intro'); } } function seconds_to_hms( seconds ) { try { var duration_hms = new Date(seconds * 1000).toISOString().substr(11, 8); return duration_hms.replace( /^00:/, '' ); } catch(e) { return ''; } } function set_current_video_to_edit( index ) { current_video_to_edit = parseInt( index ); } function set_control_fields( ajax_data ) { // add current video that we're editing into the save data ajax_data['current_video_to_edit'] = get_playlist_items_count() > 1 ? current_video_to_edit : -1; // set current editor tab for ajax_data ajax_data['current_editor_tab'] = current_editor_tab; // Used for Video Custom Fields if( current_post_id ) { ajax_data['current_post_id'] = current_post_id; } if( current_post_meta_key ) { ajax_data['current_post_meta_key'] = current_post_meta_key; } // Used in FV Player Pay Per View to store the post ID for the product ajax_data['editor_post_id'] = fv_flowplayer_set_post_thumbnail_id; return ajax_data; } // used several times below, so it's in a function function set_editor_field( key, real_val, id, video_tab ) { var real_key = map_names_to_editor_fields(key); if ( video_tab ) { $element = jQuery( '[name="' + real_key + '"]', video_tab ); } else { $element = jQuery( '[name="' + real_key + '"]' ); } // special processing for end video actions if (real_key == 'fv_wp_flowplayer_field_end_action_value') { show_end_actions( false, real_val ); return; } else if (['fv_wp_flowplayer_field_email_list', 'fv_wp_flowplayer_field_popup_id', 'fv_wp_flowplayer_field_redirect'].indexOf(real_key) > -1) { // ignore the original fields, if we still use old DB values return; } // player and video IDs wouldn't have corresponding fields if ( $element && $element.length ) { // dropdowns could have capitalized values if ($element.get(0).nodeName == 'SELECT') { if ($element.find('option[value="' + real_val + '"]').length) { $element.val(real_val); } else { // try capitalized var caps = real_val.charAt(0).toUpperCase() + real_val.slice(1); $element.find('option').each(function() { if (this.text == caps) { $element.val(caps); } }); } } else if ($element.get(0).nodeName == 'INPUT' && $element.get(0).type.toLowerCase() == 'checkbox') { let checked = real_val === '1' || real_val === 'on' || real_val === 'true' || real_val === 'yes'; // Is the value empty and is there default? if( real_val == '' && typeof(window.fv_player_editor_defaults[key]) != "undefined" ) { return; } $element.prop( 'checked', checked ).trigger('change'); } else { $element.val(real_val); } // if an ID exists, this is a meta field // and the data id needs to be added to it as well if (typeof(id) != 'undefined') { $element.attr('data-id', id); } } } function set_post_editor_content( html ) { 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) { return; } if ( instance_code_mirror ) { // set code mirror content instance_code_mirror.getDoc().setValue( html ); } else if( typeof(FCKeditorAPI) == 'undefined' && jQuery('#content:not([aria-hidden=true])').length ){ jQuery('#content:not([aria-hidden=true])').val(html); } else if ( typeof(instance_fp_wysiwyg) != 'undefined' && ( instance_tinymce == undefined || typeof tinyMCE !== 'undefined' && tinyMCE.activeEditor.isHidden() ) ) { instance_fp_wysiwyg.SetHTML( html ); } else if ( instance_tinymce ) { // instance_tinymce will be null if we're updating custom meta box instance_tinymce.setContent( html ); } } /** * Hide any overlays */ function overlay_hide() { $('.fv-player-editor-overlay').hide(); return false; } /** * Show a certain kind of overlay */ function overlay_show( type, message ) { overlay_hide(); var overlayDiv = $('#fv-player-editor-'+type+'-overlay'); overlayDiv.show(); if( typeof(message) != 'undefined' ) { overlayDiv.find('p').html( message ); } return overlayDiv; } /** * Populate content of the Embeds tab and show it if there is any content to be set * * @param string html The OL > LI list of posts which contain the same player. */ function set_embeds( html ) { // ugly way of making sure that tab stays hidden as otherwise playlist_show() would reveal it $('[data-tab=fv-player-tab-embeds]').toggleClass('always-hide',!html); get_tabs('embeds').find('td').html(html); } function shortcode_arg_split(sInput) { sInput[1] = sInput[1].replace(/\\;/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); var aInput = sInput[1].split(';'); for( var i in aInput ){ aInput[i] = aInput[i].replace(/\\"/gi, '"'); aInput[i] = aInput[i].replace(/\\/gi, ';'); aInput[i] = aInput[i].replace(//gi, ';'); } return aInput; } /* Also used by FV Player Pro and FV Player Pay Per View */ function shortcode_parse_arg( sShortcode, sArg, bHTML, sCallback ) { var rDoubleQ = new RegExp(sArg+"=\"","g"); var rSingleQ = new RegExp(sArg+"='","g"); var rNoQ = new RegExp(sArg+"=[^\"']","g"); var rMatch = false; if( sShortcode.match(rDoubleQ) ) { //rMatch = new RegExp(sArg+'="(.*?[^\\\\/])"',"g"); rMatch = new RegExp('[ "\']' + sArg + '="(.*?[^\\\\])"', "g"); } else if (sShortcode.match(rSingleQ)) { rMatch = new RegExp('[ "\']' + sArg + "='([^']*?)'", "g"); } else if (sShortcode.match(rNoQ)) { rMatch = new RegExp('[ "\']' + sArg + "=([^\\]\\s,]+)", "g"); } if( !rMatch ){ return false; } var aOutput = rMatch.exec(sShortcode); fv_player_editor.set_shortcode_remains( fv_player_editor.get_shortcode_remains().replace( rMatch, '' ) ); if( aOutput && bHTML ) { aOutput[1] = aOutput[1].replace(/\\"/g, '"').replace(/\\(\[|])/g, '$1'); } if( aOutput && sCallback ) { sCallback(aOutput); } else { return aOutput; } } function shortcode_share_parse_arg( args ) { var field = fv_player_editor.get_field("share")[0]; if (args[1] == 'yes' ) { field.selectedIndex = 1; } else if (args[1] == 'no' ) { field.selectedIndex = 2; } else { field.selectedIndex = 3; args = args[1].split(';'); if( typeof(args[0]) == "string" ) fv_player_editor.get_field('share_url').val(args[0]); if( typeof(args[1]) == "string" ) fv_player_editor.get_field('share_title').val(args[1]); fv_player_editor.get_field("share_custom").show(); } } function shortcode_subtitle_parse_arg( args ) { var input = ('shortcode_subtitle_parse_arg',args); var aLang = input[0].match(/subtitles_([a-z][a-z])/); subtitle_language_add( input[1], aLang[1] ); } function subtitle_language_add( sInput, sLang ) { language_add( 'subtitles', sInput, sLang ); } /* * Adds another language for a field * * @param {string} field Field name * @param {string} sInput Value to input * @param {string} sLang Language to use * @param {jQuery} subtitles_tab Video Subtitles tab element * @param {int} video_meta_id Field video meta ID */ function language_add( field, sInput, sLang, subtitles_tab, video_meta_id ) { // Used when parsing a legacy shortcode like [fvplayer src="video-1.mp4" subtitles="video-1.vtt"] if( typeof( subtitles_tab ) == "undefined" ){ // Reguired when parsing a legacy shortcode if( current_video_to_edit == -1 ) { current_video_to_edit = 0; } subtitles_tab = get_tab( current_video_to_edit, 'subtitles' );; } var subElement = false; // If we are loading data, do we have an empty subtitle field? if( sInput ) { // TODO: Function to get last of the language inputs which is not a child subElement = $('.fv-player-editor-field-wrap-' + field + ' > .components-base-control > .components-base-control__field:last', subtitles_tab); if( subElement.length ) { if( get_field( field,subElement).val() ) { subElement = false; } } } // If we do not have an empty subtitle field, add new if( !subElement ) { // Get the last inputs and clear the values // TODO: Function to get last of the language inputs which is not a child subElement = $( $('.fv-player-editor-field-wrap-' + field + ' > .components-base-control > .components-base-control__field:last', subtitles_tab ).prop('outerHTML') ); subElement.find('[name]').val(''); subElement.removeAttr('data-id_videometa'); // Insert the new input after the last exiting input // TODO: Function to get last of the language inputs which is not a child subElement.insertAfter( $('.fv-player-editor-field-wrap-' + field + ' > .components-base-control > .components-base-control__field:last', subtitles_tab) ); if( !sInput ) { // force user to pick the language by removing the blank value and selecting what's first subElement.find('select option[value=""]').remove(); setTimeout( function() { subElement.find('select option').eq(0).prop('selected',true); }, 0 ); } } if (typeof(video_meta_id) !== 'undefined') { subElement.attr('data-id_videometa', video_meta_id); } let new_field = get_field( field, subElement); if( sInput ) { new_field.val(sInput).trigger('change'); } show_short_link( new_field ); if ( sLang ) { if( sLang == 'iw' ) sLang = 'he'; if( sLang == 'in' ) sLang = 'id'; if( sLang == 'jw' ) sLang = 'jv'; if( sLang == 'mo' ) sLang = 'ro'; if( sLang == 'sh' ) sLang = 'sr'; get_field( field + '_lang',subElement).val(sLang).trigger('change'); } } function tabs_refresh() { var visibleTabs = 0; $el_editor.find('a[data-tab]').removeClass('fv_player_interface_hide'); $el_editor.find('.fv-player-tabs > .fv-player-tab').each(function(){ var bHideTab = true $(this).find('.fv_player_editor-panel__body, .fv-player-editor-playlist-item').each(function(){ if( $(this).css('display') === 'block' || $(this).css('display') === 'flex' || $(this).css('display') === 'table' ){ bHideTab = false; return false; } }); var tab; var data = jQuery(this).attr('class').match(/fv-player-tab-[^ ]*/); if(data[0]){ tab = $el_editor.find('a[data-tab=' + data[0] + ']'); } if( fv_player_editor_conf.tabs && fv_player_editor_conf.tabs == 'none' ) { bHideTab = true; } if(bHideTab){ tab.addClass('fv_player_interface_hide') } else { tab.removeClass('fv_player_interface_hide'); if(tab.css('display')!=='none') visibleTabs++ } }); if(visibleTabs<=1){ $el_editor.find('.nav-tab').addClass('fv_player_interface_hide'); $el_editor.find('.nav-tab-wrapper').addClass('fv_player_interface_hide'); } var end_actions_label = $('label[for=fv_wp_flowplayer_field_end_actions]'); if( $el_editor.hasClass('is-playlist-active')){ end_actions_label.html( end_actions_label.data('playlist-label') ) } else { end_actions_label.html( end_actions_label.data('single-label') ) } } /* Click handlers */ /* Click on Add Another Language (of Subtitles) */ $doc.on('click', '.add_language', function() { language_add( $( this ).data( 'field_name' ), false ); return false; }); /* Click on "Remove" to remove a language for a field */ $doc.on('click', '.remove_language', function() { let button = $( this ), field_name = button.data( 'field_name' ), field_label = button.data( 'field_label' ), field = jQuery(this).closest( '.components-base-control__field' ), id = field.attr('data-id_videometa') if( !confirm('Would you like to remove this ' + field_label + '?') ) { return false; } if (id) { deleted_video_meta.push(id); } // TODO: How to get the field wrap and how to get its langauge versions? let language_fields = get_field( field_name, true ) .closest('.components-base-control') .find( '.components-base-control__field' ); // if it's not the last subtitle, remove it completely if( language_fields.length > 1){ field.remove(); // otherwise just empty the inputs to let user add new subtitles } else { field.find('[name]').val(''); field.removeAttr('data-id_videometa'); } $doc.trigger('fv_player_editor_language_delete'); return false; }); /* Click on Import player */ $doc.on('click', '#fv-player-editor-import-overlay-import', function() { var button = this, data = jQuery('#fv_player_import_data').val(); if (!data) { fv_player_editor.overlay_notice( button, 'No data to import!', 'warning', 5000 ); return false; } try { JSON.parse(data); } catch(e) { fv_player_editor.overlay_notice( button, 'Bad JSON format!', 'error', 5000 ); return false; } fv_player_editor.overlay_notice_close_all(); overlay_show('loading'); debug_log('Running fv_player_db_import Ajax.'); jQuery.post(ajaxurl, { action: 'fv_player_db_import', nonce: fv_player_editor_conf.db_import_nonce, data: data, cookie: encodeURIComponent(document.cookie), }, function(response) { if (response != '0' && !isNaN(parseFloat(response)) && isFinite(response)) { var playerID = response; // add the inserted player's row jQuery.get( fv_player_editor_conf.admin_url + '&id=' + playerID, function (response) { jQuery('#the-list tr:first').before(jQuery(response).find('#the-list tr:first')); jQuery('.fv-wordpress-flowplayer-button').fv_player_box.close(); }).fail(function() { jQuery('.fv-wordpress-flowplayer-button').fv_player_box.close(); }); } else { fv_player_editor.overlay_notice( button, response, 'error' ); } }).fail(function() { fv_player_editor.overlay_notice( button, 'Unknown error!', 'error' ); }); return false; }); /* Editor short links */ var shortened_field_edited = false, // store field that if being edited shortened_field_button = false; // store Media Library button for the field // click on shortened preveew, show original input and hide preview $doc.on('click', '.fv_player_editor_url_shortened', function() { var preview = jQuery(this), wrap = preview.closest('.components-base-control__field'); shortened_field_edited = preview.next('.fv_player_editor_url_field') shortened_field_button = wrap.find('.add_media') preview.addClass('fv_player_interface_hide'); preview.attr('title', ''); shortened_field_button.show(); shortened_field_edited.removeClass('fv_player_interface_hide'); shortened_field_edited.focus(); }); // Clicking outside of the edited field should close the field editing $el_editor.on( 'click', function(e) { if( shortened_field_edited ) { if( !$(e.target).hasClass('fv_player_editor_url_shortened') && !$(e.target).parent().hasClass('fv_player_editor_url_shortened') && !shortened_field_edited.is(e.target) && !shortened_field_button.is(e.target) ) { show_short_link( shortened_field_edited ); } } }); // focus lost from input $doc.on('change', '.fv_player_editor_url_field', function() { show_short_link(jQuery(this)); }); $doc.on('fv_player_editor_finished fv_flowplayer_shortcode_item_switch', function() { jQuery('.fv_player_editor_url_field').each(function(index, item) { show_short_link(jQuery(item)) }); }); function show_short_link(field) { var value = field.val().trim(), preview = field.prev('.fv_player_editor_url_shortened'), button = field.closest( '.components-base-control__field').find('.add_media'); // Media Library Button shortened_field_edited = false; shortened_field_button = false; if( !value ) { // no value, hide preview field.removeClass('fv_player_interface_hide'); preview.addClass('fv_player_interface_hide'); button.show(); } else { field.addClass('fv_player_interface_hide'); preview.removeClass('fv_player_interface_hide'); preview.attr('title', value ); preview.find('.link-preview').html( shorten_original_link( value ) ); // shorten preview link button.hide(); } } function shorten_original_link(original_link) { if(!original_link) return original_link; let parts = original_link.trim().split('/'), is_hls = parts[parts.length - 1].indexOf('.m3u8') != -1, new_parts = []; $(parts).each( function(k,v) { let c = ''; // filename if( k == parts.length - 1 ) { // if it's HLS hilight the extension only if( is_hls ) { let filename_parts = v.split('.'); if( filename_parts.length == 2 ) { new_parts.push( ''+filename_parts[0]+'.'+filename_parts[1]+'' ); return true; } } // HLS filename - use the directory name } else if( is_hls && k == parts.length - 2 ) { // domain } else if ( k == 2 ) { // hilight bucket name for AWS let domain_parts = v.split('.'); if( domain_parts.length > 1 && domain_parts[1].indexOf('s3') == 0 ) { let bucket_name = domain_parts[0]; delete(domain_parts[0]) new_parts.push( ''+bucket_name+''+domain_parts.join('.')+'' ); return true; } } else { c = ' class="path"'; } new_parts.push( ''+v+'' ); }); return new_parts.join('/'); } /* End of video action */ $doc.on('change', '#fv_wp_flowplayer_field_end_actions', function() { var selected = this.value; jQuery('.fv-player-editor-field-wrap-redirect').addClass('fv_player_interface_hide'); jQuery('.fv-player-editor-field-wrap-popup_id').addClass('fv_player_interface_hide'); jQuery('.fv-player-editor-field-wrap-email_list').addClass('fv_player_interface_hide'); switch (selected) { case 'redirect': jQuery('.fv-player-editor-field-wrap-redirect').removeClass('fv_player_interface_hide'); break; case 'popup': jQuery('.fv-player-editor-field-wrap-popup_id').removeClass('fv_player_interface_hide'); break; case 'email_list': jQuery('.fv-player-editor-field-wrap-email_list').removeClass('fv_player_interface_hide'); break; default: break; } }); /* Extra fields to reveal when using a HLS or MPD stream */ $doc.on('fv_flowplayer_shortcode_item_switch', function(e, index) { show_video_details(index); show_stream_fields_worker(index); show_rtmp_fields(); show_end_of_video_actions(); show_playlist_not_supported(index); }); $doc.on('fv_flowplayer_shortcode_new', function() { show_video_details(0); show_stream_fields_worker(0); show_rtmp_fields(); show_end_of_video_actions(); show_playlist_not_supported(); }); function show_end_of_video_actions() { var player_object = get_current_player_object(), picked_action = player_object.end_actions, action_value = player_object.end_action_value; select_end_of_video_action(picked_action, action_value); } function select_end_of_video_action(action, value) { if( action == 'redirect' ) { jQuery('.fv-player-editor-field-wrap-redirect').removeClass('fv_player_interface_hide'); get_field('redirect', false).val(value); } else if ( action == 'popup' ) { jQuery('.fv-player-editor-field-wrap-popup_id').removeClass('fv_player_interface_hide'); get_field('popup', false).find('[value="'+value+'"]').prop('selected', true); } else if ( action == 'email_list' ) { jQuery('.fv-player-editor-field-wrap-email_list').removeClass('fv_player_interface_hide'); get_field('email_list', false).find('[value="'+value+'"]').prop('selected', true); } } function show_playlist_not_supported( index = 0 ) { let video = get_playlist_video_object( index ), result = { 'supported': true } if ( ! video.src ) { return; } if ( video.src.indexOf('//vimeo.com') > -1 || video.src.indexOf('//vimeopro.com') > -1 ) { result.supported = false; } // fire up a JS event for the FV Player Pro to catch, // so it can check the URL and make sure we don't show // a warning message for PRO-supported video types $doc.trigger('fv-player-editor-src-change', [ video.src, result, this ]); if( result.supported ) { playlist_buttons_disable(false); } else { // Disable the playlist editing buttons if the video type is not supported in playlists playlist_buttons_disable('FV Player Pro required for playlists with this video type'); } } function show_rtmp_fields() { var video_object = get_current_video_object(), is_enabled = video_object.rtmp || video_object.rtmp_path get_field( 'rtmp_show', true ) .prop('checked', is_enabled) .trigger('change'); if( is_enabled ) { get_field( 'toggle_advanced_settings', true ) .prop('checked', true) .trigger('change'); } } function show_stream_fields_worker( index = 0 ) { var encrypted = get_current_player_object() ? get_playlist_video_meta_value( 'encrypted', index ) : false; // hlskey var hlskey = get_field('hls_hlskey', true); if( encrypted || hlskey.val() ) { hlskey.closest('.fv_player_interface_hide').show(); } else { hlskey.closest('.fv_player_interface_hide').hide(); } // get live from video object var live_field = get_field('live', true), live_value = get_current_video_object() ? get_current_video_object().live : false, error = get_playlist_video_meta_value( 'error', index ), // Show the stream settings if it's not found and it's HLS stream // as it often returns 404 error if it's not live at the moment stream_fields_visible = !! ( error && is_live_stream( get_current_video_object() ) ); live_field.prop('checked', !!live_value); live_field.closest('.fv_player_interface_hide').toggle( !!live_value || stream_fields_visible ); checkbox_toggle_worker( jQuery(live_field).parent('.components-form-toggle'), 'live', !!live_value ); jQuery.each( [ 'audio', 'dvr' ], function(k,v) { var field = get_field(v, true), meta = get_current_player_object() ? get_playlist_video_meta_value( v, index ) : false, force_visible = stream_fields_visible, is_hls = get_current_video_object().src && get_current_video_object().src.match( /\.m3u8/ ); // Show Audio track checkbox if we have HLS with unknown dimensions if ( 'audio' === v && is_hls && get_current_video_object().width == 0 && get_current_video_object().height == 0 ) { force_visible = true; } field.prop('checked', !!meta); field.closest('.fv_player_interface_hide').toggle( !!meta || force_visible ); checkbox_toggle_worker( jQuery(field).parent('.components-form-toggle'), v, !!meta ); }); } function show_video_details( index = 0 ) { var video_tab = get_tab(index,'video-files'), video = get_playlist_video_object( index ), error = get_playlist_video_meta_value( 'error', index ) var video_info = get_field('video_info', video_tab).find('ul'), show = false; video_info.html(''); if( video.duration > 0 ) { video_info.append( '
  • ' + seconds_to_hms(video.duration) + '
  • ' ); show = true; } if( video.width > 0 && video.height > 0 ) { video_info.append( '
  • ' + video.width + 'x' + video.height + '
  • ' ); show = true; } if( video.aspect_ratio ) { video_info.append( '
  • ' + get_pretty_aspect_ratio(video) + '
  • ' ); show = true; } if( error ) { video_info.append( '
  • ' + error + '
  • ' ); show = true; if ( is_live_stream( video ) ) { video_info.append( '
  • FV Player was not able to determine the stream type, please check what applies below.
  • ' ); video_info.append( '
  • This error is normal for some of the HLS live streams if they are not currently streaming.
  • ' ); } } if( show ) { video_info.closest('.fv_player_interface_hide').removeClass('fv_player_interface_hide'); } } function checkbox_toggle_worker( wrap, name, checked ) { var compare = checked ? 1 : 0; wrap.toggleClass( 'is-checked', checked ); // If the checkox is checked its parents must be visible if( checked ) { wrap .closest('.fv-player-editor-children-wrap') .removeClass('fv_player_interface_hide'); } wrap.toggleClass( 'is-default', !!window.fv_player_editor_defaults[name] ); if( window.fv_player_editor_dependencies[name] ) { jQuery.each( window.fv_player_editor_dependencies[name], function(value,inputs) { jQuery.each( inputs, function(k,input_name) { // We get the last matching field as if we are loading a playlist, we might have multiple fields // TODO: Do this more sensibly, like when switching playlist items let field_wrap = $('.fv-player-editor-field-wrap-' + input_name + ':last' ); // TODO: What should be saved when it's enabled? field_wrap.toggleClass( 'is-visible-dependency', value == compare ); field_wrap.toggleClass( 'is-hidden-dependency', value != compare ); }); }); } wrap.closest( '.fv-player-editor-children-wrap' ).find( '.fv-player-editor-field-children-'+name ).toggle( checked ); } function text_and_select_worker( input, parent, name, wrap ) { // Reveal the input if it has value even if it's not enabled in Post Interface options if ( input.val() ) { // Only if it's not field with shortening if ( input.prev( '.fv_player_editor_url_shortened' ).length === 0 ) { input.closest( '.fv_player_interface_hide' ).show(); } wrap.show(); } // Show children inputs if input has value parent.find('.fv-player-editor-field-children-' + name ).toggle( !! input.val() ); } function show_end_actions( e, value ) { // redirect, popup and email_list var type = jQuery('#fv_wp_flowplayer_field_end_actions').val(); jQuery('.fv_player_actions_end-toggle').hide().find('[name]').val(''); // The field id is different for popup if( type == 'popup' ) { type = 'popup_id'; } var field = jQuery('#fv_wp_flowplayer_field_' + type); field.parents('tr').show(); if( value ) { field.val( value ).trigger( 'change' ); } } function init_saved_player_fields() { deleted_videos = []; deleted_video_meta = []; deleted_player_meta = []; } function title_editor_close() { $('.fv-player-editor-playlist-item .fvp_item_video-title-wrap').show(); $('.fv-player-editor-playlist-item .fvp_item_video-edit-input').hide(); } /* Mark each manually updated title or splash field as such */ $doc.on('input change', '#fv_wp_flowplayer_field_splash, #fv_wp_flowplayer_field_title', function() { if ( is_loading ) { return; } let $input; // if this element already has data set, don't do any of the selections below if (typeof( jQuery(this).data('fv_player_user_updated') ) != 'undefined') { return; } if( this.id == 'fv_wp_flowplayer_field_splash' ) { $input = get_field('auto_splash', true); } else { $input = get_field('auto_caption', true); } if( $input.length > 0 ) { $input.val(0); debug_log(this.id+' has been updated manually!'); } }); // Public stuff return { add_notice, playlist_index, reload_preview( index ) { return reload_preview(index); }, fv_wp_flowplayer_dialog_resize, get_current_player_db_id, get_current_player_object, get_current_video_meta, get_current_video_meta_value, get_current_video_object, get_current_video_index() { return parseInt( current_video_to_edit ); }, get_current_video_db_id() { return current_video_db_id; }, get_edit_lock_removal() { return edit_lock_removal; }, get_field( key, where ) { return get_field( key, where ); }, get_playlist_video_meta, get_playlist_video_meta_value, get_playlist_video_object, get_shortcode_remains: function() { return shortcode_remains; }, get_tab( index, tab ) { return get_tab( index, tab ); }, insertUpdateOrDeletePlayerMeta, insertUpdateOrDeleteVideoMeta, is_busy_saving() { return ajax_save_this_please || is_saving; }, // Allows external entities to set which video will be edited once the editor opens set_current_video_to_edit, set_edit_lock_removal( val ) { edit_lock_removal = val; }, set_shortcode_remains: function(value) { shortcode_remains = value; }, /** * Show a notice in the overlay above the editor * * @param {Object} button The button in the overlay that was clicked * Used to find the overlay and the notice in it * @param {string} html Content of the notice * @param {string} type success|error * @param {int} close_after Optional number of miliseconds to close the * notice after * */ overlay_notice: function(button, html, type, close_after ) { var overlay = jQuery(button).closest('.fv-player-editor-overlay'), notice = overlay.find('.fv-player-editor-overlay-notice'); notice .html(html) .removeClass('notice-error') .removeClass('notice-success') .addClass('notice-'+type) .css('visibility', 'visible'); overlay.show(); if( close_after ) { setTimeout(function() { notice.css('visibility', 'hidden'); }, close_after); } }, overlay_notice_close_all: function() { $('.fv-player-editor-overlay-notice').css('visibility', 'hidden'); }, playlist_item_add: playlist_item_add, playlist_show: playlist_show, shortcode_parse_arg, // We keep it for backwards compatibility editor_resize: function() {}, get_playlist_items_count, insert_button_toggle, copy_player_button_toggle, // TODO: Deprecated meta_data_load_started() { is_loading_meta_data++; }, meta_data_load_finished() { is_loading_meta_data--; }, editor_open: editor_open, error_add( identifier, txt ) { errors[ identifier ] = txt; // no save button while we have errors insert_button_toggle_disabled( true ); }, error_remove( identifier ) { delete errors[ identifier ]; if ( !has_errors() ) { // enable save button insert_button_toggle_disabled( false ); } }, errors_remove( prefix ) { var errors_found = []; for ( var key in errors ) { if ( key.startsWith( prefix ) ) { errors_found.push( key ); } } if ( errors_found.length ) { for ( var val of errors_found ) { delete errors[ val ]; } } if ( has_errors() ) { // enable save button insert_button_toggle_disabled( false ); } }, has_errors, b64EncodeUnicode(str) { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { return String.fromCharCode('0x' + p1); })); }, }; })(jQuery); if ( window.wp && wp.data ) { wp.data.subscribe( function() { if ( wp.data.select('core/editor') && wp.data.select('core/editor').getCurrentPost().fv_player_reload ) { setTimeout( function() { location.href = location.href; }, 0 ); } } ); } });