PageRenderTime 79ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/Wordpress-Project/wp-content/plugins/siteorigin-panels/js/siteorigin-panels-2516.js

https://bitbucket.org/hinzanhilmy/hinzan-sample-works
JavaScript | 7235 lines | 4846 code | 1214 blank | 1175 comment | 663 complexity | c0c96c6ae32c83abe5891ef5a5f779af MD5 | raw file
Possible License(s): GPL-3.0, 0BSD, Apache-2.0, BSD-2-Clause, MPL-2.0-no-copyleft-exception, LGPL-2.1, GPL-2.0, BSD-3-Clause, MIT
  1. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  2. var panels = window.panels;
  3. module.exports = Backbone.Collection.extend( {
  4. model: panels.model.cell,
  5. initialize: function () {
  6. },
  7. /**
  8. * Get the total weight for the cells in this collection.
  9. * @returns {number}
  10. */
  11. totalWeight: function () {
  12. var totalWeight = 0;
  13. this.each( function ( cell ) {
  14. totalWeight += cell.get( 'weight' );
  15. } );
  16. return totalWeight;
  17. },
  18. } );
  19. },{}],2:[function(require,module,exports){
  20. var panels = window.panels;
  21. module.exports = Backbone.Collection.extend( {
  22. model: panels.model.historyEntry,
  23. /**
  24. * The builder model
  25. */
  26. builder: null,
  27. /**
  28. * The maximum number of items in the history
  29. */
  30. maxSize: 12,
  31. initialize: function () {
  32. this.on( 'add', this.onAddEntry, this );
  33. },
  34. /**
  35. * Add an entry to the collection.
  36. *
  37. * @param text The text that defines the action taken to get to this
  38. * @param data
  39. */
  40. addEntry: function ( text, data ) {
  41. if ( _.isEmpty( data ) ) {
  42. data = this.builder.getPanelsData();
  43. }
  44. var entry = new panels.model.historyEntry( {
  45. text: text,
  46. data: JSON.stringify( data ),
  47. time: parseInt( new Date().getTime() / 1000 ),
  48. collection: this
  49. } );
  50. this.add( entry );
  51. },
  52. /**
  53. * Resize the collection so it's not bigger than this.maxSize
  54. */
  55. onAddEntry: function ( entry ) {
  56. if ( this.models.length > 1 ) {
  57. var lastEntry = this.at( this.models.length - 2 );
  58. if (
  59. (
  60. entry.get( 'text' ) === lastEntry.get( 'text' ) && entry.get( 'time' ) - lastEntry.get( 'time' ) < 15
  61. ) ||
  62. (
  63. entry.get( 'data' ) === lastEntry.get( 'data' )
  64. )
  65. ) {
  66. // If both entries have the same text and are within 20 seconds of each other, or have the same data, then remove most recent
  67. this.remove( entry );
  68. lastEntry.set( 'count', lastEntry.get( 'count' ) + 1 );
  69. }
  70. }
  71. // Make sure that there are not to many entries in this collection
  72. while ( this.models.length > this.maxSize ) {
  73. this.shift();
  74. }
  75. }
  76. } );
  77. },{}],3:[function(require,module,exports){
  78. var panels = window.panels;
  79. module.exports = Backbone.Collection.extend( {
  80. model: panels.model.row,
  81. /**
  82. * Destroy all the rows in this collection
  83. */
  84. empty: function () {
  85. var model;
  86. do {
  87. model = this.collection.first();
  88. if ( ! model ) {
  89. break;
  90. }
  91. model.destroy();
  92. } while ( true );
  93. }
  94. } );
  95. },{}],4:[function(require,module,exports){
  96. var panels = window.panels;
  97. module.exports = Backbone.Collection.extend( {
  98. model: panels.model.widget,
  99. initialize: function () {
  100. }
  101. } );
  102. },{}],5:[function(require,module,exports){
  103. var panels = window.panels, $ = jQuery;
  104. module.exports = panels.view.dialog.extend( {
  105. dialogClass: 'so-panels-dialog-add-builder',
  106. render: function () {
  107. // Render the dialog and attach it to the builder interface
  108. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-builder' ).html(), {} ) );
  109. this.$( '.so-content .siteorigin-panels-builder' ).append( this.builder.$el );
  110. },
  111. initializeDialog: function () {
  112. var thisView = this;
  113. this.once( 'open_dialog_complete', function () {
  114. thisView.builder.initSortable();
  115. } );
  116. this.on( 'open_dialog_complete', function () {
  117. thisView.builder.trigger( 'builder_resize' );
  118. } );
  119. }
  120. } );
  121. },{}],6:[function(require,module,exports){
  122. var panels = window.panels, $ = jQuery;
  123. module.exports = panels.view.dialog.extend( {
  124. historyEntryTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-history-entry' ).html() ) ),
  125. entries: {},
  126. currentEntry: null,
  127. revertEntry: null,
  128. selectedEntry: null,
  129. previewScrollTop: null,
  130. dialogClass: 'so-panels-dialog-history',
  131. dialogIcon: 'history',
  132. events: {
  133. 'click .so-close': 'closeDialog',
  134. 'click .so-restore': 'restoreSelectedEntry'
  135. },
  136. initializeDialog: function () {
  137. this.entries = new panels.collection.historyEntries();
  138. this.on( 'open_dialog', this.setCurrentEntry, this );
  139. this.on( 'open_dialog', this.renderHistoryEntries, this );
  140. },
  141. render: function () {
  142. var thisView = this;
  143. // Render the dialog and attach it to the builder interface
  144. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-history' ).html(), {} ) );
  145. this.$( 'iframe.siteorigin-panels-history-iframe' ).load( function () {
  146. var $$ = $( this );
  147. $$.show();
  148. $$.contents().scrollTop( thisView.previewScrollTop );
  149. } );
  150. },
  151. /**
  152. * Set the original entry. This should be set when creating the dialog.
  153. *
  154. * @param {panels.model.builder} builder
  155. */
  156. setRevertEntry: function ( builder ) {
  157. this.revertEntry = new panels.model.historyEntry( {
  158. data: JSON.stringify( builder.getPanelsData() ),
  159. time: parseInt( new Date().getTime() / 1000 )
  160. } );
  161. },
  162. /**
  163. * This is triggered when the dialog is opened.
  164. */
  165. setCurrentEntry: function () {
  166. this.currentEntry = new panels.model.historyEntry( {
  167. data: JSON.stringify( this.builder.model.getPanelsData() ),
  168. time: parseInt( new Date().getTime() / 1000 )
  169. } );
  170. this.selectedEntry = this.currentEntry;
  171. this.previewEntry( this.currentEntry );
  172. this.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
  173. },
  174. /**
  175. * Render the history entries in the sidebar
  176. */
  177. renderHistoryEntries: function () {
  178. // Set up an interval that will display the time since every 10 seconds
  179. var thisView = this;
  180. var c = this.$( '.history-entries' ).empty();
  181. if ( this.currentEntry.get( 'data' ) !== this.revertEntry.get( 'data' ) || ! _.isEmpty( this.entries.models ) ) {
  182. $( this.historyEntryTemplate( {title: panelsOptions.loc.history.revert, count: 1} ) )
  183. .data( 'historyEntry', this.revertEntry )
  184. .prependTo( c );
  185. }
  186. // Now load all the entries in this.entries
  187. this.entries.each( function ( entry ) {
  188. var html = thisView.historyEntryTemplate( {
  189. title: panelsOptions.loc.history[entry.get( 'text' )],
  190. count: entry.get( 'count' )
  191. } );
  192. $( html )
  193. .data( 'historyEntry', entry )
  194. .prependTo( c );
  195. } );
  196. $( this.historyEntryTemplate( {title: panelsOptions.loc.history['current'], count: 1} ) )
  197. .data( 'historyEntry', this.currentEntry )
  198. .addClass( 'so-selected' )
  199. .prependTo( c );
  200. // Handle loading and selecting
  201. c.find( '.history-entry' ).click( function () {
  202. var $$ = jQuery( this );
  203. c.find( '.history-entry' ).not( $$ ).removeClass( 'so-selected' );
  204. $$.addClass( 'so-selected' );
  205. var entry = $$.data( 'historyEntry' );
  206. thisView.selectedEntry = entry;
  207. if ( thisView.selectedEntry.cid !== thisView.currentEntry.cid ) {
  208. thisView.$( '.so-buttons .so-restore' ).removeClass( 'disabled' );
  209. } else {
  210. thisView.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
  211. }
  212. thisView.previewEntry( entry );
  213. } );
  214. this.updateEntryTimes();
  215. },
  216. /**
  217. * Preview an entry
  218. *
  219. * @param entry
  220. */
  221. previewEntry: function ( entry ) {
  222. var iframe = this.$( 'iframe.siteorigin-panels-history-iframe' );
  223. iframe.hide();
  224. this.previewScrollTop = iframe.contents().scrollTop();
  225. this.$( 'form.history-form input[name="live_editor_panels_data"]' ).val( entry.get( 'data' ) );
  226. this.$( 'form.history-form input[name="live_editor_post_ID"]' ).val( this.builder.config.postId );
  227. this.$( 'form.history-form' ).submit();
  228. },
  229. /**
  230. * Restore the current entry
  231. */
  232. restoreSelectedEntry: function () {
  233. if ( this.$( '.so-buttons .so-restore' ).hasClass( 'disabled' ) ) {
  234. return false;
  235. }
  236. if ( this.currentEntry.get( 'data' ) === this.selectedEntry.get( 'data' ) ) {
  237. this.closeDialog();
  238. return false;
  239. }
  240. // Add an entry for this restore event
  241. if ( this.selectedEntry.get( 'text' ) !== 'restore' ) {
  242. this.builder.addHistoryEntry( 'restore', this.builder.model.getPanelsData() );
  243. }
  244. this.builder.model.loadPanelsData( JSON.parse( this.selectedEntry.get( 'data' ) ) );
  245. this.closeDialog();
  246. return false;
  247. },
  248. /**
  249. * Update the entry times for the list of entries down the side
  250. */
  251. updateEntryTimes: function () {
  252. var thisView = this;
  253. this.$( '.history-entries .history-entry' ).each( function () {
  254. var $$ = jQuery( this );
  255. var time = $$.find( '.timesince' );
  256. var entry = $$.data( 'historyEntry' );
  257. time.html( thisView.timeSince( entry.get( 'time' ) ) );
  258. } );
  259. },
  260. /**
  261. * Gets the time since as a nice string.
  262. *
  263. * @param date
  264. */
  265. timeSince: function ( time ) {
  266. var diff = parseInt( new Date().getTime() / 1000 ) - time;
  267. var parts = [];
  268. var interval;
  269. // There are 3600 seconds in an hour
  270. if ( diff > 3600 ) {
  271. interval = Math.floor( diff / 3600 );
  272. if ( interval === 1 ) {
  273. parts.push( panelsOptions.loc.time.hour.replace( '%d', interval ) );
  274. } else {
  275. parts.push( panelsOptions.loc.time.hours.replace( '%d', interval ) );
  276. }
  277. diff -= interval * 3600;
  278. }
  279. // There are 60 seconds in a minute
  280. if ( diff > 60 ) {
  281. interval = Math.floor( diff / 60 );
  282. if ( interval === 1 ) {
  283. parts.push( panelsOptions.loc.time.minute.replace( '%d', interval ) );
  284. } else {
  285. parts.push( panelsOptions.loc.time.minutes.replace( '%d', interval ) );
  286. }
  287. diff -= interval * 60;
  288. }
  289. if ( diff > 0 ) {
  290. if ( diff === 1 ) {
  291. parts.push( panelsOptions.loc.time.second.replace( '%d', diff ) );
  292. } else {
  293. parts.push( panelsOptions.loc.time.seconds.replace( '%d', diff ) );
  294. }
  295. }
  296. // Return the amount of time ago
  297. return _.isEmpty( parts ) ? panelsOptions.loc.time.now : panelsOptions.loc.time.ago.replace( '%s', parts.slice( 0, 2 ).join( ', ' ) );
  298. }
  299. } );
  300. },{}],7:[function(require,module,exports){
  301. var panels = window.panels, $ = jQuery;
  302. module.exports = panels.view.dialog.extend( {
  303. directoryTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-directory-items' ).html() ) ),
  304. builder: null,
  305. dialogClass: 'so-panels-dialog-prebuilt-layouts',
  306. dialogIcon: 'layouts',
  307. layoutCache: {},
  308. currentTab: false,
  309. directoryPage: 1,
  310. events: {
  311. 'click .so-close': 'closeDialog',
  312. 'click .so-sidebar-tabs li a': 'tabClickHandler',
  313. 'click .so-content .layout': 'layoutClickHandler',
  314. 'keyup .so-sidebar-search': 'searchHandler',
  315. // The directory items
  316. 'click .so-screenshot, .so-title': 'directoryItemClickHandler'
  317. },
  318. /**
  319. * Initialize the prebuilt dialog.
  320. */
  321. initializeDialog: function () {
  322. var thisView = this;
  323. this.on( 'open_dialog', function () {
  324. thisView.$( '.so-sidebar-tabs li a' ).first().click();
  325. thisView.$( '.so-status' ).removeClass( 'so-panels-loading' );
  326. } );
  327. this.on( 'button_click', this.toolbarButtonClick, this );
  328. },
  329. /**
  330. * Render the prebuilt layouts dialog
  331. */
  332. render: function () {
  333. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-prebuilt' ).html(), {} ) );
  334. this.initToolbar();
  335. },
  336. /**
  337. *
  338. * @param e
  339. * @return {boolean}
  340. */
  341. tabClickHandler: function ( e ) {
  342. e.preventDefault();
  343. // Reset selected item state when changing tabs
  344. this.selectedLayoutItem = null;
  345. this.uploadedLayout = null;
  346. this.updateButtonState( false );
  347. this.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
  348. var $$ = $( e.target );
  349. var tab = $$.attr( 'href' ).split( '#' )[1];
  350. $$.parent().addClass( 'tab-active' );
  351. var thisView = this;
  352. // Empty everything
  353. this.$( '.so-content' ).empty();
  354. thisView.currentTab = tab;
  355. if ( tab == 'import' ) {
  356. this.displayImportExport();
  357. } else {
  358. this.displayLayoutDirectory( '', 1, tab );
  359. }
  360. thisView.$( '.so-sidebar-search' ).val( '' );
  361. },
  362. /**
  363. * Display and setup the import/export form
  364. */
  365. displayImportExport: function () {
  366. var c = this.$( '.so-content' ).empty().removeClass( 'so-panels-loading' );
  367. c.html( $( '#siteorigin-panels-dialog-prebuilt-importexport' ).html() );
  368. var thisView = this;
  369. var uploadUi = thisView.$( '.import-upload-ui' ).hide();
  370. // Create the uploader
  371. var uploader = new plupload.Uploader( {
  372. runtimes: 'html5,silverlight,flash,html4',
  373. browse_button: uploadUi.find( '.file-browse-button' ).get( 0 ),
  374. container: uploadUi.get( 0 ),
  375. drop_element: uploadUi.find( '.drag-upload-area' ).get( 0 ),
  376. file_data_name: 'panels_import_data',
  377. multiple_queues: false,
  378. max_file_size: panelsOptions.plupload.max_file_size,
  379. url: panelsOptions.plupload.url,
  380. flash_swf_url: panelsOptions.plupload.flash_swf_url,
  381. silverlight_xap_url: panelsOptions.plupload.silverlight_xap_url,
  382. filters: [
  383. {title: panelsOptions.plupload.filter_title, extensions: 'json'}
  384. ],
  385. multipart_params: {
  386. action: 'so_panels_import_layout'
  387. },
  388. init: {
  389. PostInit: function ( uploader ) {
  390. if ( uploader.features.dragdrop ) {
  391. uploadUi.addClass( 'has-drag-drop' );
  392. }
  393. uploadUi.show().find( '.progress-precent' ).css( 'width', '0%' );
  394. },
  395. FilesAdded: function ( uploader ) {
  396. uploadUi.find( '.file-browse-button' ).blur();
  397. uploadUi.find( '.drag-upload-area' ).removeClass( 'file-dragover' );
  398. uploadUi.find( '.progress-bar' ).fadeIn( 'fast' );
  399. thisView.$( '.js-so-selected-file' ).text( panelsOptions.loc.prebuilt_loading );
  400. uploader.start();
  401. },
  402. UploadProgress: function ( uploader, file ) {
  403. uploadUi.find( '.progress-precent' ).css( 'width', file.percent + '%' );
  404. },
  405. FileUploaded: function ( uploader, file, response ) {
  406. var layout = JSON.parse( response.response );
  407. if ( ! _.isUndefined( layout.widgets ) ) {
  408. thisView.uploadedLayout = layout;
  409. uploadUi.find( '.progress-bar' ).hide();
  410. thisView.$( '.js-so-selected-file' ).text(
  411. panelsOptions.loc.ready_to_insert.replace( '%s', file.name )
  412. );
  413. thisView.updateButtonState( true );
  414. } else {
  415. alert( panelsOptions.plupload.error_message );
  416. }
  417. },
  418. Error: function () {
  419. alert( panelsOptions.plupload.error_message );
  420. }
  421. }
  422. } );
  423. uploader.init();
  424. // This is
  425. uploadUi.find( '.drag-upload-area' )
  426. .on( 'dragover', function () {
  427. $( this ).addClass( 'file-dragover' );
  428. } )
  429. .on( 'dragleave', function () {
  430. $( this ).removeClass( 'file-dragover' );
  431. } );
  432. // Handle exporting the file
  433. c.find( '.so-export' ).submit( function ( e ) {
  434. var $$ = $( this );
  435. $$.find( 'input[name="panels_export_data"]' ).val( JSON.stringify( thisView.builder.model.getPanelsData() ) );
  436. } );
  437. },
  438. /**
  439. * Display the layout directory tab.
  440. *
  441. * @param query
  442. */
  443. displayLayoutDirectory: function ( search, page, type ) {
  444. var thisView = this;
  445. var c = this.$( '.so-content' ).empty().addClass( 'so-panels-loading' );
  446. if ( search === undefined ) {
  447. search = '';
  448. }
  449. if ( page === undefined ) {
  450. page = 1;
  451. }
  452. if ( type === undefined ) {
  453. type = 'directory-siteorigin';
  454. }
  455. if ( type.match('^directory-') && ! panelsOptions.directory_enabled ) {
  456. // Display the button to enable the prebuilt layout
  457. c.removeClass( 'so-panels-loading' ).html( $( '#siteorigin-panels-directory-enable' ).html() );
  458. c.find( '.so-panels-enable-directory' ).click( function ( e ) {
  459. e.preventDefault();
  460. // Sent the query to enable the directory, then enable the directory
  461. $.get(
  462. panelsOptions.ajaxurl,
  463. {action: 'so_panels_directory_enable'},
  464. function () {
  465. }
  466. );
  467. // Enable the layout directory
  468. panelsOptions.directory_enabled = true;
  469. c.addClass( 'so-panels-loading' );
  470. thisView.displayLayoutDirectory( search, page, type );
  471. } );
  472. return;
  473. }
  474. // Get all the items for the current query
  475. $.get(
  476. panelsOptions.ajaxurl,
  477. {
  478. action: 'so_panels_layouts_query',
  479. search: search,
  480. page: page,
  481. type: type,
  482. },
  483. function ( data ) {
  484. // Skip this if we're no longer viewing the layout directory
  485. if ( thisView.currentTab !== type ) {
  486. return;
  487. }
  488. // Add the directory items
  489. c.removeClass( 'so-panels-loading' ).html( thisView.directoryTemplate( data ) );
  490. // Lets setup the next and previous buttons
  491. var prev = c.find( '.so-previous' ), next = c.find( '.so-next' );
  492. if ( page <= 1 ) {
  493. prev.addClass( 'button-disabled' );
  494. } else {
  495. prev.click( function ( e ) {
  496. e.preventDefault();
  497. thisView.displayLayoutDirectory( search, page - 1, thisView.currentTab );
  498. } );
  499. }
  500. if ( page === data.max_num_pages || data.max_num_pages === 0 ) {
  501. next.addClass( 'button-disabled' );
  502. } else {
  503. next.click( function ( e ) {
  504. e.preventDefault();
  505. thisView.displayLayoutDirectory( search, page + 1, thisView.currentTab );
  506. } );
  507. }
  508. // Handle nice preloading of the screenshots
  509. c.find( '.so-screenshot' ).each( function () {
  510. var $$ = $( this ), $a = $$.find( '.so-screenshot-wrapper' );
  511. $a.css( 'height', ( $a.width() / 4 * 3 ) + 'px' ).addClass( 'so-loading' );
  512. if ( $$.data( 'src' ) !== '' ) {
  513. // Set the initial height
  514. var $img = $( '<img/>' ).attr( 'src', $$.data( 'src' ) ).load( function () {
  515. $a.removeClass( 'so-loading' ).css( 'height', 'auto' );
  516. $img.appendTo( $a ).hide().fadeIn( 'fast' );
  517. } );
  518. } else {
  519. $( '<img/>' ).attr( 'src', panelsOptions.prebuiltDefaultScreenshot ).appendTo( $a ).hide().fadeIn( 'fast' );
  520. }
  521. } );
  522. // Set the title
  523. c.find( '.so-directory-browse' ).html( data.title );
  524. },
  525. 'json'
  526. );
  527. },
  528. /**
  529. * Set the selected state for the clicked layout directory item and remove previously selected item.
  530. * Enable the toolbar buttons.
  531. */
  532. directoryItemClickHandler: function ( e ) {
  533. var $directoryItem = this.$( e.target ).closest( '.so-directory-item' );
  534. this.$( '.so-directory-items' ).find( '.selected' ).removeClass( 'selected' );
  535. $directoryItem.addClass( 'selected' );
  536. this.selectedLayoutItem = {lid: $directoryItem.data( 'layout-id' ), type: $directoryItem.data( 'layout-type' )};
  537. this.updateButtonState( true );
  538. },
  539. /**
  540. * Load a particular layout into the builder.
  541. *
  542. * @param id
  543. */
  544. toolbarButtonClick: function ( $button ) {
  545. if ( ! this.canAddLayout() ) {
  546. return false;
  547. }
  548. var position = $button.data( 'value' );
  549. if ( _.isUndefined( position ) ) {
  550. return false;
  551. }
  552. this.updateButtonState( false );
  553. if ( $button.hasClass( 'so-needs-confirm' ) && ! $button.hasClass( 'so-confirmed' ) ) {
  554. this.updateButtonState( true );
  555. if ( $button.hasClass( 'so-confirming' ) ) {
  556. return;
  557. }
  558. $button.addClass( 'so-confirming' );
  559. var originalText = $button.html();
  560. $button.html( '<span class="dashicons dashicons-yes"></span>' + $button.data( 'confirm' ) );
  561. setTimeout( function () {
  562. $button.removeClass( 'so-confirmed' ).html( originalText );
  563. }, 2500 );
  564. setTimeout( function () {
  565. $button.removeClass( 'so-confirming' );
  566. $button.addClass( 'so-confirmed' );
  567. }, 200 );
  568. return false;
  569. }
  570. this.addingLayout = true;
  571. if ( this.currentTab === 'import' ) {
  572. this.addLayoutToBuilder( this.uploadedLayout, position );
  573. } else {
  574. this.loadSelectedLayout().then( function ( layout ) {
  575. this.addLayoutToBuilder( layout, position );
  576. }.bind( this ) );
  577. }
  578. },
  579. canAddLayout: function () {
  580. return (
  581. this.selectedLayoutItem || this.uploadedLayout
  582. ) && ! this.addingLayout;
  583. },
  584. /**
  585. * Load the layout according to selectedLayoutItem.
  586. */
  587. loadSelectedLayout: function () {
  588. this.setStatusMessage( panelsOptions.loc.prebuilt_loading, true );
  589. var args = _.extend( this.selectedLayoutItem, {action: 'so_panels_get_layout'} );
  590. var deferredLayout = new $.Deferred();
  591. $.get(
  592. panelsOptions.ajaxurl,
  593. args,
  594. function ( layout ) {
  595. if ( layout.error !== undefined ) {
  596. // There was an error
  597. alert( layout.error );
  598. deferredLayout.reject( layout );
  599. } else {
  600. this.setStatusMessage( '', false );
  601. deferredLayout.resolve( layout );
  602. }
  603. }.bind( this )
  604. );
  605. return deferredLayout.promise();
  606. },
  607. /**
  608. * Handle an update to the search
  609. */
  610. searchHandler: function ( e ) {
  611. if ( e.keyCode === 13 ) {
  612. this.displayLayoutDirectory( $( e.currentTarget ).val(), 1, this.currentTab );
  613. }
  614. },
  615. /**
  616. * Attempt to set the 'Insert' button's state according to the `enabled` argument, also checking whether the
  617. * requirements for inserting a layout have valid values.
  618. */
  619. updateButtonState: function ( enabled ) {
  620. enabled = enabled && (
  621. this.selectedLayoutItem || this.uploadedLayout
  622. );
  623. var $button = this.$( '.so-import-layout' );
  624. $button.prop( "disabled", ! enabled );
  625. if ( enabled ) {
  626. $button.removeClass( 'disabled' );
  627. } else {
  628. $button.addClass( 'disabled' );
  629. }
  630. },
  631. addLayoutToBuilder: function ( layout, position ) {
  632. this.builder.addHistoryEntry( 'prebuilt_loaded' );
  633. this.builder.model.loadPanelsData( layout, position );
  634. this.addingLayout = false;
  635. this.closeDialog();
  636. }
  637. } );
  638. },{}],8:[function(require,module,exports){
  639. var panels = window.panels, $ = jQuery;
  640. module.exports = panels.view.dialog.extend({
  641. cellPreviewTemplate: _.template( panels.helpers.utils.processTemplate( $('#siteorigin-panels-dialog-row-cell-preview').html() ) ),
  642. editableLabel: true,
  643. events: {
  644. 'click .so-close': 'closeDialog',
  645. // Toolbar buttons
  646. 'click .so-toolbar .so-save': 'saveHandler',
  647. 'click .so-toolbar .so-insert': 'insertHandler',
  648. 'click .so-toolbar .so-delete': 'deleteHandler',
  649. 'click .so-toolbar .so-duplicate': 'duplicateHandler',
  650. // Changing the row
  651. 'change .row-set-form > *': 'setCellsFromForm',
  652. 'click .row-set-form button.set-row': 'setCellsFromForm',
  653. },
  654. dialogIcon: 'add-row',
  655. dialogClass: 'so-panels-dialog-row-edit',
  656. styleType: 'row',
  657. dialogType: 'edit',
  658. /**
  659. * The current settings, not yet saved to the model
  660. */
  661. row: {
  662. // This will be a clone of cells collection.
  663. cells: null,
  664. // The style settings of the row
  665. style: {}
  666. },
  667. cellStylesCache: [],
  668. initializeDialog: function () {
  669. this.on('open_dialog', function () {
  670. if (!_.isUndefined(this.model) && !_.isEmpty(this.model.get('cells'))) {
  671. this.setRowModel(this.model);
  672. } else {
  673. this.setRowModel(null);
  674. }
  675. this.regenerateRowPreview();
  676. }, this);
  677. // This is the default row layout
  678. this.row = {
  679. cells: new panels.collection.cells([{weight: 0.5}, {weight: 0.5}]),
  680. style: {}
  681. };
  682. // Refresh panels data after both dialog form components are loaded
  683. this.dialogFormsLoaded = 0;
  684. var thisView = this;
  685. this.on('form_loaded styles_loaded', function () {
  686. this.dialogFormsLoaded++;
  687. if (this.dialogFormsLoaded === 2) {
  688. thisView.updateModel({
  689. refreshArgs: {
  690. silent: true
  691. }
  692. });
  693. }
  694. });
  695. this.on('close_dialog', this.closeHandler);
  696. this.on( 'edit_label', function ( text ) {
  697. // If text is set to default values, just clear it.
  698. if ( text === panelsOptions.loc.row.add || text === panelsOptions.loc.row.edit ) {
  699. text = '';
  700. }
  701. this.model.set( 'label', text );
  702. if ( _.isEmpty( text ) ) {
  703. var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
  704. this.$( '.so-title').text( title );
  705. }
  706. }.bind( this ) );
  707. },
  708. /**
  709. *
  710. * @param dialogType Either "edit" or "create"
  711. */
  712. setRowDialogType: function (dialogType) {
  713. this.dialogType = dialogType;
  714. },
  715. /**
  716. * Render the new row dialog
  717. */
  718. render: function () {
  719. var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
  720. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-row' ).html(), {
  721. title: title,
  722. dialogType: this.dialogType
  723. } ) );
  724. var titleElt = this.$( '.so-title' );
  725. if ( this.model.has( 'label' ) && ! _.isEmpty( this.model.get( 'label' ) ) ) {
  726. titleElt.text( this.model.get( 'label' ) );
  727. }
  728. this.$( '.so-edit-title' ).val( titleElt.text() );
  729. // Now we need to attach the style window
  730. this.styles = new panels.view.styles();
  731. this.styles.model = this.model;
  732. this.styles.render('row', this.builder.config.postId, {
  733. builderType: this.builder.config.builderType,
  734. dialog: this
  735. });
  736. if (!this.builder.supports('addRow')) {
  737. this.$('.so-buttons .so-duplicate').remove();
  738. }
  739. if (!this.builder.supports('deleteRow')) {
  740. this.$('.so-buttons .so-delete').remove();
  741. }
  742. var $rightSidebar = this.$('.so-sidebar.so-right-sidebar');
  743. this.styles.attach($rightSidebar);
  744. // Handle the loading class
  745. this.styles.on('styles_loaded', function (hasStyles) {
  746. // If we have styles remove the loading spinner, else remove the whole empty sidebar.
  747. if (hasStyles) {
  748. $rightSidebar.removeClass('so-panels-loading');
  749. } else {
  750. $rightSidebar.closest('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
  751. $rightSidebar.remove();
  752. }
  753. }, this);
  754. $rightSidebar.addClass('so-panels-loading');
  755. if (!_.isUndefined(this.model)) {
  756. // Set the initial value of the
  757. this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
  758. if ( this.model.has( 'ratio' ) ) {
  759. this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
  760. }
  761. if ( this.model.has( 'ratio_direction' ) ) {
  762. this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
  763. }
  764. }
  765. this.$('input.so-row-field').keyup(function () {
  766. $(this).trigger('change');
  767. });
  768. return this;
  769. },
  770. /**
  771. * Set the row model we'll be using for this dialog.
  772. *
  773. * @param model
  774. */
  775. setRowModel: function (model) {
  776. this.model = model;
  777. if (_.isEmpty(this.model)) {
  778. return this;
  779. }
  780. // Set the rows to be a copy of the model
  781. this.row = {
  782. cells: this.model.get('cells').clone(),
  783. style: {},
  784. ratio: this.model.get('ratio'),
  785. ratio_direction: this.model.get('ratio_direction'),
  786. };
  787. // Set the initial value of the cell field.
  788. this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
  789. if ( this.model.has( 'ratio' ) ) {
  790. this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
  791. }
  792. if ( this.model.has( 'ratio_direction' ) ) {
  793. this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
  794. }
  795. this.clearCellStylesCache();
  796. return this;
  797. },
  798. /**
  799. * Regenerate the row preview and resizing interface.
  800. */
  801. regenerateRowPreview: function () {
  802. var thisDialog = this;
  803. var rowPreview = this.$('.row-preview');
  804. // If no selected cell, select the first cell.
  805. var selectedIndex = this.getSelectedCellIndex();
  806. rowPreview.empty();
  807. var timeout;
  808. // Represent the cells
  809. this.row.cells.each(function (cellModel, i) {
  810. var newCell = $(this.cellPreviewTemplate({weight: cellModel.get('weight')}));
  811. rowPreview.append(newCell);
  812. if(i == selectedIndex) {
  813. newCell.find('.preview-cell-in').addClass('cell-selected');
  814. }
  815. var prevCell = newCell.prev();
  816. var handle;
  817. if (prevCell.length) {
  818. handle = $('<div class="resize-handle"></div>');
  819. handle
  820. .appendTo(newCell)
  821. .dblclick(function () {
  822. var prevCellModel = thisDialog.row.cells.at(i - 1);
  823. var t = cellModel.get('weight') + prevCellModel.get('weight');
  824. cellModel.set('weight', t / 2);
  825. prevCellModel.set('weight', t / 2);
  826. thisDialog.scaleRowWidths();
  827. });
  828. handle.draggable({
  829. axis: 'x',
  830. containment: rowPreview,
  831. start: function (e, ui) {
  832. // Create the clone for the current cell
  833. var newCellClone = newCell.clone().appendTo(ui.helper).css({
  834. position: 'absolute',
  835. top: '0',
  836. width: newCell.outerWidth(),
  837. left: 6,
  838. height: newCell.outerHeight()
  839. });
  840. newCellClone.find('.resize-handle').remove();
  841. // Create the clone for the previous cell
  842. var prevCellClone = prevCell.clone().appendTo(ui.helper).css({
  843. position: 'absolute',
  844. top: '0',
  845. width: prevCell.outerWidth(),
  846. right: 6,
  847. height: prevCell.outerHeight()
  848. });
  849. prevCellClone.find('.resize-handle').remove();
  850. $(this).data({
  851. 'newCellClone': newCellClone,
  852. 'prevCellClone': prevCellClone
  853. });
  854. // Hide the
  855. newCell.find('> .preview-cell-in').css('visibility', 'hidden');
  856. prevCell.find('> .preview-cell-in').css('visibility', 'hidden');
  857. },
  858. drag: function (e, ui) {
  859. // Calculate the new cell and previous cell widths as a percent
  860. var cellWeight = thisDialog.row.cells.at(i).get('weight');
  861. var prevCellWeight = thisDialog.row.cells.at(i - 1).get('weight');
  862. var ncw = cellWeight - (
  863. (
  864. ui.position.left + 6
  865. ) / rowPreview.width()
  866. );
  867. var pcw = prevCellWeight + (
  868. (
  869. ui.position.left + 6
  870. ) / rowPreview.width()
  871. );
  872. var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6;
  873. $(this).data('newCellClone').css('width', rowPreview.width() * ncw)
  874. .find('.preview-cell-weight').html(Math.round(ncw * 1000) / 10);
  875. $(this).data('prevCellClone').css('width', rowPreview.width() * pcw)
  876. .find('.preview-cell-weight').html(Math.round(pcw * 1000) / 10);
  877. },
  878. stop: function (e, ui) {
  879. // Remove the clones
  880. $(this).data('newCellClone').remove();
  881. $(this).data('prevCellClone').remove();
  882. // Reshow the main cells
  883. newCell.find('.preview-cell-in').css('visibility', 'visible');
  884. prevCell.find('.preview-cell-in').css('visibility', 'visible');
  885. // Calculate the new cell weights
  886. var offset = ui.position.left + 6;
  887. var percent = offset / rowPreview.width();
  888. // Ignore this if any of the cells are below 2% in width.
  889. var cellModel = thisDialog.row.cells.at(i);
  890. var prevCellModel = thisDialog.row.cells.at(i - 1);
  891. if (cellModel.get('weight') - percent > 0.02 && prevCellModel.get('weight') + percent > 0.02) {
  892. cellModel.set('weight', cellModel.get('weight') - percent);
  893. prevCellModel.set('weight', prevCellModel.get('weight') + percent);
  894. }
  895. thisDialog.scaleRowWidths();
  896. ui.helper.css('left', -6);
  897. }
  898. });
  899. }
  900. newCell.click(function (event) {
  901. if ( ! ( $(event.target).is('.preview-cell') || $(event.target).is('.preview-cell-in') ) ) {
  902. return;
  903. }
  904. var cell = $(event.target);
  905. cell.closest('.row-preview').find('.preview-cell .preview-cell-in').removeClass('cell-selected');
  906. cell.addClass('cell-selected');
  907. this.openSelectedCellStyles();
  908. }.bind(this));
  909. // Make this row weight click editable
  910. newCell.find('.preview-cell-weight').click(function (ci) {
  911. // Disable the draggable while entering values
  912. thisDialog.$('.resize-handle').css('pointer-event', 'none').draggable('disable');
  913. rowPreview.find('.preview-cell-weight').each(function () {
  914. var $$ = jQuery(this).hide();
  915. $('<input type="text" class="preview-cell-weight-input no-user-interacted" />')
  916. .val(parseFloat($$.html())).insertAfter($$)
  917. .focus(function () {
  918. clearTimeout(timeout);
  919. })
  920. .keyup(function (e) {
  921. if (e.keyCode !== 9) {
  922. // Only register the interaction if the user didn't press tab
  923. $(this).removeClass('no-user-interacted');
  924. }
  925. // Enter is clicked
  926. if (e.keyCode === 13) {
  927. e.preventDefault();
  928. $(this).blur();
  929. }
  930. })
  931. .keydown(function (e) {
  932. if (e.keyCode === 9) {
  933. e.preventDefault();
  934. // Tab will always cycle around the row inputs
  935. var inputs = rowPreview.find('.preview-cell-weight-input');
  936. var i = inputs.index($(this));
  937. if (i === inputs.length - 1) {
  938. inputs.eq(0).focus().select();
  939. } else {
  940. inputs.eq(i + 1).focus().select();
  941. }
  942. }
  943. })
  944. .blur(function () {
  945. rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
  946. if (isNaN(parseFloat($(el).val()))) {
  947. $(el).val(Math.floor(thisDialog.row.cells.at(i).get('weight') * 1000) / 10);
  948. }
  949. });
  950. timeout = setTimeout(function () {
  951. // If there are no weight inputs, then skip this
  952. if (rowPreview.find('.preview-cell-weight-input').length === 0) {
  953. return false;
  954. }
  955. // Go through all the inputs
  956. var rowWeights = [],
  957. rowChanged = [],
  958. changedSum = 0,
  959. unchangedSum = 0;
  960. rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
  961. var val = parseFloat($(el).val());
  962. if (isNaN(val)) {
  963. val = 1 / thisDialog.row.cells.length;
  964. } else {
  965. val = Math.round(val * 10) / 1000;
  966. }
  967. // Check within 3 decimal points
  968. var changed = !$(el).hasClass('no-user-interacted');
  969. rowWeights.push(val);
  970. rowChanged.push(changed);
  971. if (changed) {
  972. changedSum += val;
  973. } else {
  974. unchangedSum += val;
  975. }
  976. });
  977. if (changedSum > 0 && unchangedSum > 0 && (
  978. 1 - changedSum
  979. ) > 0) {
  980. // Balance out the unchanged rows to occupy the weight left over by the changed sum
  981. for (var i = 0; i < rowWeights.length; i++) {
  982. if (!rowChanged[i]) {
  983. rowWeights[i] = (
  984. rowWeights[i] / unchangedSum
  985. ) * (
  986. 1 - changedSum
  987. );
  988. }
  989. }
  990. }
  991. // Last check to ensure total weight is 1
  992. var sum = _.reduce(rowWeights, function (memo, num) {
  993. return memo + num;
  994. });
  995. rowWeights = rowWeights.map(function (w) {
  996. return w / sum;
  997. });
  998. // Set the new cell weights and regenerate the preview.
  999. if (Math.min.apply(Math, rowWeights) > 0.01) {
  1000. thisDialog.row.cells.each(function (cell, i) {
  1001. cell.set('weight', rowWeights[i]);
  1002. });
  1003. }
  1004. // Now lets animate the cells into their new widths
  1005. rowPreview.find('.preview-cell').each(function (i, el) {
  1006. var cellWeight = thisDialog.row.cells.at(i).get('weight');
  1007. $(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
  1008. $(el).find('.preview-cell-weight-input').val(Math.round(cellWeight * 1000) / 10);
  1009. });
  1010. // So the draggable handle is not hidden.
  1011. rowPreview.find('.preview-cell').css('overflow', 'visible');
  1012. setTimeout(function () {
  1013. thisDialog.regenerateRowPreview();
  1014. }, 260);
  1015. }, 100);
  1016. })
  1017. .click(function () {
  1018. $(this).select();
  1019. });
  1020. });
  1021. $(this).siblings('.preview-cell-weight-input').select();
  1022. });
  1023. }, this);
  1024. this.openSelectedCellStyles();
  1025. this.trigger('form_loaded', this);
  1026. },
  1027. getSelectedCellIndex: function() {
  1028. var selectedIndex = -1;
  1029. this.$('.preview-cell .preview-cell-in').each(function(index, el) {
  1030. if($(el).is('.cell-selected')) {
  1031. selectedIndex = index;
  1032. }
  1033. });
  1034. return selectedIndex;
  1035. },
  1036. openSelectedCellStyles: function() {
  1037. if (!_.isUndefined(this.cellStyles)) {
  1038. if (this.cellStyles.stylesLoaded) {
  1039. var style = {};
  1040. try {
  1041. style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
  1042. }
  1043. catch (err) {
  1044. console.log('Error retrieving cell styles - ' + err.message);
  1045. }
  1046. this.cellStyles.model.set('style', style);
  1047. }
  1048. this.cellStyles.detach();
  1049. }
  1050. this.cellStyles = this.getSelectedCellStyles();
  1051. if ( this.cellStyles ) {
  1052. var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
  1053. this.cellStyles.attach( $rightSidebar );
  1054. if ( !this.cellStyles.stylesLoaded ) {
  1055. this.cellStyles.on( 'styles_loaded', function () {
  1056. $rightSidebar.removeClass( 'so-panels-loading' );
  1057. }, this );
  1058. $rightSidebar.addClass( 'so-panels-loading' );
  1059. }
  1060. }
  1061. },
  1062. getSelectedCellStyles: function () {
  1063. var cellIndex = this.getSelectedCellIndex();
  1064. if ( cellIndex > -1 ) {
  1065. var cellStyles = this.cellStylesCache[cellIndex];
  1066. if ( !cellStyles ) {
  1067. cellStyles = new panels.view.styles();
  1068. cellStyles.model = this.row.cells.at( cellIndex );
  1069. cellStyles.render( 'cell', this.builder.config.postId, {
  1070. builderType: this.builder.config.builderType,
  1071. dialog: this,
  1072. index: cellIndex,
  1073. } );
  1074. this.cellStylesCache[cellIndex] = cellStyles;
  1075. }
  1076. }
  1077. return cellStyles;
  1078. },
  1079. clearCellStylesCache: function () {
  1080. // Call remove() on all cell styles to remove data, event listeners etc.
  1081. this.cellStylesCache.forEach(function (cellStyles) {
  1082. cellStyles.remove();
  1083. });
  1084. this.cellStylesCache = [];
  1085. },
  1086. /**
  1087. * Visually scale the row widths based on the cell weights
  1088. */
  1089. scaleRowWidths: function () {
  1090. var thisDialog = this;
  1091. this.$('.row-preview .preview-cell').each(function (i, el) {
  1092. var cell = thisDialog.row.cells.at(i);
  1093. $(el)
  1094. .css('width', cell.get('weight') * 100 + "%")
  1095. .find('.preview-cell-weight').html(Math.round(cell.get('weight') * 1000) / 10);
  1096. });
  1097. },
  1098. /**
  1099. * Get the weights from the
  1100. */
  1101. setCellsFromForm: function () {
  1102. try {
  1103. var f = {
  1104. 'cells': parseInt(this.$('.row-set-form input[name="cells"]').val()),
  1105. 'ratio': parseFloat(this.$('.row-set-form select[name="ratio"]').val()),
  1106. 'direction': this.$('.row-set-form select[name="ratio_direction"]').val()
  1107. };
  1108. if (_.isNaN(f.cells)) {
  1109. f.cells = 1;
  1110. }
  1111. if (isNaN(f.ratio)) {
  1112. f.ratio = 1;
  1113. }
  1114. if (f.cells < 1) {
  1115. f.cells = 1;
  1116. this.$('.row-set-form input[name="cells"]').val(f.cells);
  1117. }
  1118. else if (f.cells > 12) {
  1119. f.cells = 12;
  1120. this.$('.row-set-form input[name="cells"]').val(f.cells);
  1121. }
  1122. this.$('.row-set-form select[name="ratio"]').val(f.ratio);
  1123. var cells = [];
  1124. var cellCountChanged = (
  1125. this.row.cells.length !== f.cells
  1126. );
  1127. // Now, lets create some cells
  1128. var currentWeight = 1;
  1129. for (var i = 0; i < f.cells; i++) {
  1130. cells.push(currentWeight);
  1131. currentWeight *= f.ratio;
  1132. }
  1133. // Now lets make sure that the row weights add up to 1
  1134. var totalRowWeight = _.reduce(cells, function (memo, weight) {
  1135. return memo + weight;
  1136. });
  1137. cells = _.map(cells, function (cell) {
  1138. return cell / totalRowWeight;
  1139. });
  1140. // Don't return cells that are too small
  1141. cells = _.filter(cells, function (cell) {
  1142. return cell > 0.01;
  1143. });
  1144. if (f.direction === 'left') {
  1145. cells = cells.reverse();
  1146. }
  1147. // Discard deleted cells.
  1148. this.row.cells = new panels.collection.cells(this.row.cells.first(cells.length));
  1149. _.each(cells, function (cellWeight, index) {
  1150. var cell = this.row.cells.at(index);
  1151. if (!cell) {
  1152. cell = new panels.model.cell({weight: cellWeight, row: this.model});
  1153. this.row.cells.add(cell);
  1154. } else {
  1155. cell.set('weight', cellWeight);
  1156. }
  1157. }.bind(this));
  1158. this.row.ratio = f.ratio;
  1159. this.row.ratio_direction = f.direction;
  1160. if (cellCountChanged) {
  1161. this.regenerateRowPreview();
  1162. } else {
  1163. var thisDialog = this;
  1164. // Now lets animate the cells into their new widths
  1165. this.$('.preview-cell').each(function (i, el) {
  1166. var cellWeight = thisDialog.row.cells.at(i).get('weight');
  1167. $(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
  1168. $(el).find('.preview-cell-weight').html(Math.round(cellWeight * 1000) / 10);
  1169. });
  1170. // So the draggable handle is not hidden.
  1171. this.$('.preview-cell').css('overflow', 'visible');
  1172. setTimeout(function () {
  1173. thisDialog.regenerateRowPreview();
  1174. }, 260);
  1175. }
  1176. }
  1177. catch (err) {
  1178. console.log('Error setting cells - ' + err.message);
  1179. }
  1180. // Remove the button primary class
  1181. this.$('.row-set-form .so-button-row-set').removeClass('button-primary');
  1182. },
  1183. /**
  1184. * Handle a click on the dialog left bar tab
  1185. */
  1186. tabClickHandler: function ($t) {
  1187. if ($t.attr('href') === '#row-layout') {
  1188. this.$('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
  1189. } else {
  1190. this.$('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
  1191. }
  1192. },
  1193. /**
  1194. * Update the current model with what we have in the dialog
  1195. */
  1196. updateModel: function (args) {
  1197. args = _.extend({
  1198. refresh: true,
  1199. refreshArgs: null
  1200. }, args);
  1201. // Set the cells
  1202. if (!_.isEmpty(this.model)) {
  1203. this.model.setCells( this.row.cells );
  1204. this.model.set( 'ratio', this.row.ratio );
  1205. this.model.set( 'ratio_direction', this.row.ratio_direction );
  1206. }
  1207. // Update the row styles if they've loaded
  1208. if (!_.isUndefined(this.styles) && this.styles.stylesLoaded) {
  1209. // This is an edit dialog, so there are styles
  1210. var style = {};
  1211. try {
  1212. style = this.getFormValues('.so-sidebar .so-visual-styles.so-row-styles').style;
  1213. }
  1214. catch (err) {
  1215. console.log('Error retrieving row styles - ' + err.message);
  1216. }
  1217. this.model.set('style', style);
  1218. }
  1219. // Update the cell styles if any are showing.
  1220. if (!_.isUndefined(this.cellStyles) && this.cellStyles.stylesLoaded) {
  1221. var style = {};
  1222. try {
  1223. style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
  1224. }
  1225. catch (err) {
  1226. console.log('Error retrieving cell styles - ' + err.message);
  1227. }
  1228. this.cellStyles.model.set('style', style);
  1229. }
  1230. if (args.refresh) {
  1231. this.builder.model.refreshPanelsData(args.refreshArgs);
  1232. }
  1233. },
  1234. /**
  1235. * Insert the new row
  1236. */
  1237. insertHandler: function () {
  1238. this.builder.addHistoryEntry('row_added');
  1239. this.updateModel();
  1240. var activeCell = this.builder.getActiveCell({
  1241. createCell: false,
  1242. });
  1243. var options = {};
  1244. if (activeCell !== null) {
  1245. options.at = this.builder.model.get('rows').indexOf(activeCell.row) + 1;
  1246. }
  1247. // Set up the model and add it to the builder
  1248. this.model.collection = this.builder.model.get('rows');
  1249. this.builder.model.get('rows').add(this.model, options);
  1250. this.closeDialog();
  1251. this.builder.model.refreshPanelsData();
  1252. return false;
  1253. },
  1254. /**
  1255. * We'll just save this model and close the dialog
  1256. */
  1257. saveHandler: function () {
  1258. this.builder.addHistoryEntry('row_edited');
  1259. this.updateModel();
  1260. this.closeDialog();
  1261. this.builder.model.refreshPanelsData();
  1262. return false;
  1263. },
  1264. /**
  1265. * The user clicks delete, so trigger deletion on the row model
  1266. */
  1267. deleteHandler: function () {
  1268. // Trigger a destroy on the model that will happen with a visual indication to the user
  1269. this.model.trigger('visual_destroy');
  1270. this.closeDialog({silent: true});
  1271. return false;
  1272. },
  1273. /**
  1274. * Duplicate this row
  1275. */
  1276. duplicateHandler: function () {
  1277. this.builder.addHistoryEntry('row_duplicated');
  1278. var duplicateRow = this.model.clone(this.builder.model);
  1279. this.builder.model.get('rows').add( duplicateRow, {
  1280. at: this.builder.model.get('rows').indexOf(this.model) + 1
  1281. } );
  1282. this.closeDialog({silent: true});
  1283. return false;
  1284. },
  1285. closeHandler: function() {
  1286. this.clearCellStylesCache();
  1287. if( ! _.isUndefined(this.cellStyles) ) {
  1288. this.cellStyles = undefined;
  1289. }
  1290. },
  1291. });
  1292. },{}],9:[function(require,module,exports){
  1293. var panels = window.panels, $ = jQuery;
  1294. var jsWidget = require( '../view/widgets/js-widget' );
  1295. module.exports = panels.view.dialog.extend( {
  1296. builder: null,
  1297. sidebarWidgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widget-sidebar-widget' ).html() ) ),
  1298. dialogClass: 'so-panels-dialog-edit-widget',
  1299. dialogIcon: 'add-widget',
  1300. widgetView: false,
  1301. savingWidget: false,
  1302. editableLabel: true,
  1303. events: {
  1304. 'click .so-close': 'saveHandler',
  1305. 'click .so-nav.so-previous': 'navToPrevious',
  1306. 'click .so-nav.so-next': 'navToNext',
  1307. // Action handlers
  1308. 'click .so-toolbar .so-delete': 'deleteHandler',
  1309. 'click .so-toolbar .so-duplicate': 'duplicateHandler'
  1310. },
  1311. initializeDialog: function () {
  1312. var thisView = this;
  1313. this.model.on( 'change:values', this.handleChangeValues, this );
  1314. this.model.on( 'destroy', this.remove, this );
  1315. // Refresh panels data after both dialog form components are loaded
  1316. this.dialogFormsLoaded = 0;
  1317. this.on( 'form_loaded styles_loaded', function () {
  1318. this.dialogFormsLoaded ++;
  1319. if ( this.dialogFormsLoaded === 2 ) {
  1320. thisView.updateModel( {
  1321. refreshArgs: {
  1322. silent: true
  1323. }
  1324. } );
  1325. }
  1326. } );
  1327. this.on( 'edit_label', function ( text ) {
  1328. // If text is set to default value, just clear it.
  1329. if ( text === panelsOptions.widgets[ this.model.get( 'class' ) ][ 'title' ] ) {
  1330. text = '';
  1331. }
  1332. this.model.set( 'label', text );
  1333. if ( _.isEmpty( text ) ) {
  1334. this.$( '.so-title' ).text( this.model.getWidgetField( 'title' ) );
  1335. }
  1336. }.bind( this ) );
  1337. },
  1338. /**
  1339. * Render the widget dialog.
  1340. */
  1341. render: function () {
  1342. // Render the dialog and attach it to the builder interface
  1343. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widget' ).html(), {} ) );
  1344. this.loadForm();
  1345. var title = this.model.getWidgetField( 'title' );
  1346. this.$( '.so-title .widget-name' ).html( title );
  1347. this.$( '.so-edit-title' ).val( title );
  1348. if( ! this.builder.supports( 'addWidget' ) ) {
  1349. this.$( '.so-buttons .so-duplicate' ).remove();
  1350. }
  1351. if( ! this.builder.supports( 'deleteWidget' ) ) {
  1352. this.$( '.so-buttons .so-delete' ).remove();
  1353. }
  1354. // Now we need to attach the style window
  1355. this.styles = new panels.view.styles();
  1356. this.styles.model = this.model;
  1357. this.styles.render( 'widget', this.builder.config.postId, {
  1358. builderType: this.builder.config.builderType,
  1359. dialog: this
  1360. } );
  1361. var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
  1362. this.styles.attach( $rightSidebar );
  1363. // Handle the loading class
  1364. this.styles.on( 'styles_loaded', function ( hasStyles ) {
  1365. // If we have styles remove the loading spinner, else remove the whole empty sidebar.
  1366. if ( hasStyles ) {
  1367. $rightSidebar.removeClass( 'so-panels-loading' );
  1368. } else {
  1369. $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  1370. $rightSidebar.remove();
  1371. }
  1372. }, this );
  1373. $rightSidebar.addClass( 'so-panels-loading' );
  1374. },
  1375. /**
  1376. * Get the previous widget editing dialog by looking at the dom.
  1377. * @returns {*}
  1378. */
  1379. getPrevDialog: function () {
  1380. var widgets = this.builder.$( '.so-cells .cell .so-widget' );
  1381. if ( widgets.length <= 1 ) {
  1382. return false;
  1383. }
  1384. var currentIndex = widgets.index( this.widgetView.$el );
  1385. if ( currentIndex === 0 ) {
  1386. return false;
  1387. } else {
  1388. do {
  1389. widgetView = widgets.eq( --currentIndex ).data( 'view' );
  1390. if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
  1391. return widgetView.getEditDialog();
  1392. }
  1393. } while( ! _.isUndefined( widgetView ) && currentIndex > 0 );
  1394. }
  1395. return false;
  1396. },
  1397. /**
  1398. * Get the next widget editing dialog by looking at the dom.
  1399. * @returns {*}
  1400. */
  1401. getNextDialog: function () {
  1402. var widgets = this.builder.$( '.so-cells .cell .so-widget' );
  1403. if ( widgets.length <= 1 ) {
  1404. return false;
  1405. }
  1406. var currentIndex = widgets.index( this.widgetView.$el ), widgetView;
  1407. if ( currentIndex === widgets.length - 1 ) {
  1408. return false;
  1409. } else {
  1410. do {
  1411. widgetView = widgets.eq( ++currentIndex ).data( 'view' );
  1412. if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
  1413. return widgetView.getEditDialog();
  1414. }
  1415. } while( ! _.isUndefined( widgetView ) );
  1416. }
  1417. return false;
  1418. },
  1419. /**
  1420. * Load the widget form from the server.
  1421. * This is called when rendering the dialog for the first time.
  1422. */
  1423. loadForm: function () {
  1424. // don't load the form if this dialog hasn't been rendered yet
  1425. if ( ! this.$( '> *' ).length ) {
  1426. return;
  1427. }
  1428. this.$( '.so-content' ).addClass( 'so-panels-loading' );
  1429. var data = {
  1430. 'action': 'so_panels_widget_form',
  1431. 'widget': this.model.get( 'class' ),
  1432. 'instance': JSON.stringify( this.model.get( 'values' ) ),
  1433. 'raw': this.model.get( 'raw' )
  1434. };
  1435. $.post(
  1436. panelsOptions.ajaxurl,
  1437. data,
  1438. function ( result ) {
  1439. // Add in the CID of the widget model
  1440. var html = result.replace( /{\$id}/g, this.model.cid );
  1441. // Load this content into the form
  1442. var $soContent = this.$( '.so-content' );
  1443. $soContent
  1444. .removeClass( 'so-panels-loading' )
  1445. .html( html );
  1446. // Trigger all the necessary events
  1447. this.trigger( 'form_loaded', this );
  1448. // For legacy compatibility, trigger a panelsopen event
  1449. this.$( '.panel-dialog' ).trigger( 'panelsopen' );
  1450. // If the main dialog is closed from this point on, save the widget content
  1451. this.on( 'close_dialog', this.updateModel, this );
  1452. var widgetContent = $soContent.find( '> .widget-content' );
  1453. // If there's a widget content wrapper, this is one of the new widgets in WP 4.8 which need some special
  1454. // handling in JS.
  1455. if ( widgetContent.length > 0 ) {
  1456. jsWidget.addWidget( $soContent, this.model.widget_id );
  1457. }
  1458. }.bind( this ),
  1459. 'html'
  1460. );
  1461. },
  1462. /**
  1463. * Save the widget from the form to the model
  1464. */
  1465. updateModel: function ( args ) {
  1466. args = _.extend( {
  1467. refresh: true,
  1468. refreshArgs: null
  1469. }, args );
  1470. // Get the values from the form and assign the new values to the model
  1471. this.savingWidget = true;
  1472. if ( ! this.model.get( 'missing' ) ) {
  1473. // Only get the values for non missing widgets.
  1474. var values = this.getFormValues();
  1475. if ( _.isUndefined( values.widgets ) ) {
  1476. values = {};
  1477. } else {
  1478. values = values.widgets;
  1479. values = values[Object.keys( values )[0]];
  1480. }
  1481. this.model.setValues( values );
  1482. this.model.set( 'raw', true ); // We've saved from the widget form, so this is now raw
  1483. }
  1484. if ( this.styles.stylesLoaded ) {
  1485. // If the styles view has loaded
  1486. var style = {};
  1487. try {
  1488. style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
  1489. }
  1490. catch ( e ) {
  1491. }
  1492. this.model.set( 'style', style );
  1493. }
  1494. this.savingWidget = false;
  1495. if ( args.refresh ) {
  1496. this.builder.model.refreshPanelsData( args.refreshArgs );
  1497. }
  1498. },
  1499. /**
  1500. *
  1501. */
  1502. handleChangeValues: function () {
  1503. if ( ! this.savingWidget ) {
  1504. // Reload the form when we've changed the model and we're not currently saving from the form
  1505. this.loadForm();
  1506. }
  1507. },
  1508. /**
  1509. * Save a history entry for this widget. Called when the dialog is closed.
  1510. */
  1511. saveHandler: function () {
  1512. this.builder.addHistoryEntry( 'widget_edited' );
  1513. this.closeDialog();
  1514. },
  1515. /**
  1516. * When the user clicks delete.
  1517. *
  1518. * @returns {boolean}
  1519. */
  1520. deleteHandler: function () {
  1521. this.model.trigger( 'visual_destroy' );
  1522. this.closeDialog( {silent: true} );
  1523. this.builder.model.refreshPanelsData();
  1524. return false;
  1525. },
  1526. duplicateHandler: function () {
  1527. this.model.trigger( 'user_duplicate' );
  1528. this.closeDialog( {silent: true} );
  1529. this.builder.model.refreshPanelsData();
  1530. return false;
  1531. }
  1532. } );
  1533. },{"../view/widgets/js-widget":31}],10:[function(require,module,exports){
  1534. var panels = window.panels, $ = jQuery;
  1535. module.exports = panels.view.dialog.extend( {
  1536. builder: null,
  1537. widgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widgets-widget' ).html() ) ),
  1538. filter: {},
  1539. dialogClass: 'so-panels-dialog-add-widget',
  1540. dialogIcon: 'add-widget',
  1541. events: {
  1542. 'click .so-close': 'closeDialog',
  1543. 'click .widget-type': 'widgetClickHandler',
  1544. 'keyup .so-sidebar-search': 'searchHandler'
  1545. },
  1546. /**
  1547. * Initialize the widget adding dialog
  1548. */
  1549. initializeDialog: function () {
  1550. this.on( 'open_dialog', function () {
  1551. this.filter.search = '';
  1552. this.filterWidgets( this.filter );
  1553. }, this );
  1554. this.on( 'open_dialog_complete', function () {
  1555. // Clear the search and re-filter the widgets when we open the dialog
  1556. this.$( '.so-sidebar-search' ).val( '' ).focus();
  1557. this.balanceWidgetHeights();
  1558. } );
  1559. // We'll implement a custom tab click handler
  1560. this.on( 'tab_click', this.tabClickHandler, this );
  1561. },
  1562. render: function () {
  1563. // Render the dialog and attach it to the builder interface
  1564. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widgets' ).html(), {} ) );
  1565. // Add all the widgets
  1566. _.each( panelsOptions.widgets, function ( widget ) {
  1567. var $w = $( this.widgetTemplate( {
  1568. title: widget.title,
  1569. description: widget.description
  1570. } ) );
  1571. if ( _.isUndefined( widget.icon ) ) {
  1572. widget.icon = 'dashicons dashicons-admin-generic';
  1573. }
  1574. $( '<span class="widget-icon" />' ).addClass( widget.icon ).prependTo( $w.find( '.widget-type-wrapper' ) );
  1575. $w.data( 'class', widget.class ).appendTo( this.$( '.widget-type-list' ) );
  1576. }, this );
  1577. // Add the sidebar tabs
  1578. var tabs = this.$( '.so-sidebar-tabs' );
  1579. _.each( panelsOptions.widget_dialog_tabs, function ( tab ) {
  1580. $( this.dialogTabTemplate( {'title': tab.title} ) ).data( {
  1581. 'message': tab.message,
  1582. 'filter': tab.filter
  1583. } ).appendTo( tabs );
  1584. }, this );
  1585. // We'll be using tabs, so initialize them
  1586. this.initTabs();
  1587. var thisDialog = this;
  1588. $( window ).resize( function () {
  1589. thisDialog.balanceWidgetHeights();
  1590. } );
  1591. },
  1592. /**
  1593. * Handle a tab being clicked
  1594. */
  1595. tabClickHandler: function ( $t ) {
  1596. // Get the filter from the tab, and filter the widgets
  1597. this.filter = $t.parent().data( 'filter' );
  1598. this.filter.search = this.$( '.so-sidebar-search' ).val();
  1599. var message = $t.parent().data( 'message' );
  1600. if ( _.isEmpty( message ) ) {
  1601. message = '';
  1602. }
  1603. this.$( '.so-toolbar .so-status' ).html( message );
  1604. this.filterWidgets( this.filter );
  1605. return false;
  1606. },
  1607. /**
  1608. * Handle changes to the search value
  1609. */
  1610. searchHandler: function ( e ) {
  1611. if( e.which === 13 ) {
  1612. var visibleWidgets = this.$( '.widget-type-list .widget-type:visible' );
  1613. if( visibleWidgets.length === 1 ) {
  1614. visibleWidgets.click();
  1615. }
  1616. }
  1617. else {
  1618. this.filter.search = $( e.target ).val().trim();
  1619. this.filterWidgets( this.filter );
  1620. }
  1621. },
  1622. /**
  1623. * Filter the widgets that we're displaying
  1624. * @param filter
  1625. */
  1626. filterWidgets: function ( filter ) {
  1627. if ( _.isUndefined( filter ) ) {
  1628. filter = {};
  1629. }
  1630. if ( _.isUndefined( filter.groups ) ) {
  1631. filter.groups = '';
  1632. }
  1633. this.$( '.widget-type-list .widget-type' ).each( function () {
  1634. var $$ = $( this ), showWidget;
  1635. var widgetClass = $$.data( 'class' );
  1636. var widgetData = (
  1637. ! _.isUndefined( panelsOptions.widgets[widgetClass] )
  1638. ) ? panelsOptions.widgets[widgetClass] : null;
  1639. if ( _.isEmpty( filter.groups ) ) {
  1640. // This filter doesn't specify groups, so show all
  1641. showWidget = true;
  1642. } else if ( widgetData !== null && ! _.isEmpty( _.intersection( filter.groups, panelsOptions.widgets[widgetClass].groups ) ) ) {
  1643. // This widget is in the filter group
  1644. showWidget = true;
  1645. } else {
  1646. // This widget is not in the filter group
  1647. showWidget = false;
  1648. }
  1649. // This can probably be done with a more intelligent operator
  1650. if ( showWidget ) {
  1651. if ( ! _.isUndefined( filter.search ) && filter.search !== '' ) {
  1652. // Check if the widget title contains the search term
  1653. if ( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) === - 1 ) {
  1654. showWidget = false;
  1655. }
  1656. }
  1657. }
  1658. if ( showWidget ) {
  1659. $$.show();
  1660. } else {
  1661. $$.hide();
  1662. }
  1663. } );
  1664. // Balance the tags after filtering
  1665. this.balanceWidgetHeights();
  1666. },
  1667. /**
  1668. * Add the widget to the current builder
  1669. *
  1670. * @param e
  1671. */
  1672. widgetClickHandler: function ( e ) {
  1673. // Add the history entry
  1674. this.builder.addHistoryEntry( 'widget_added' );
  1675. var $w = $( e.currentTarget );
  1676. var widget = new panels.model.widget( {
  1677. class: $w.data( 'class' )
  1678. } );
  1679. // Add the widget to the cell model
  1680. widget.cell = this.builder.getActiveCell();
  1681. widget.cell.get('widgets').add( widget );
  1682. this.closeDialog();
  1683. this.builder.model.refreshPanelsData();
  1684. },
  1685. /**
  1686. * Balance widgets in a given row so they have enqual height.
  1687. * @param e
  1688. */
  1689. balanceWidgetHeights: function ( e ) {
  1690. var widgetRows = [[]];
  1691. var previousWidget = null;
  1692. // Work out how many widgets there are per row
  1693. var perRow = Math.round( this.$( '.widget-type' ).parent().width() / this.$( '.widget-type' ).width() );
  1694. // Add clears to create balanced rows
  1695. this.$( '.widget-type' )
  1696. .css( 'clear', 'none' )
  1697. .filter( ':visible' )
  1698. .each( function ( i, el ) {
  1699. if ( i % perRow === 0 && i !== 0 ) {
  1700. $( el ).css( 'clear', 'both' );
  1701. }
  1702. } );
  1703. // Group the widgets into rows
  1704. this.$( '.widget-type-wrapper' )
  1705. .css( 'height', 'auto' )
  1706. .filter( ':visible' )
  1707. .each( function ( i, el ) {
  1708. var $el = $( el );
  1709. if ( previousWidget !== null && previousWidget.position().top !== $el.position().top ) {
  1710. widgetRows[widgetRows.length] = [];
  1711. }
  1712. previousWidget = $el;
  1713. widgetRows[widgetRows.length - 1].push( $el );
  1714. } );
  1715. // Balance the height of the widgets within the row.
  1716. _.each( widgetRows, function ( row, i ) {
  1717. var maxHeight = _.max( row.map( function ( el ) {
  1718. return el.height();
  1719. } ) );
  1720. // Set the height of each widget in the row
  1721. _.each( row, function ( el ) {
  1722. el.height( maxHeight );
  1723. } );
  1724. } );
  1725. }
  1726. } );
  1727. },{}],11:[function(require,module,exports){
  1728. module.exports = {
  1729. /**
  1730. * Check if we have copy paste available.
  1731. * @returns {boolean|*}
  1732. */
  1733. canCopyPaste: function(){
  1734. return typeof(Storage) !== "undefined" && panelsOptions.user;
  1735. },
  1736. /**
  1737. * Set the model that we're going to store in the clipboard
  1738. */
  1739. setModel: function( model ){
  1740. if( ! this.canCopyPaste() ) {
  1741. return false;
  1742. }
  1743. var serial = panels.helpers.serialize.serialize( model );
  1744. if( model instanceof panels.model.row ) {
  1745. serial.thingType = 'row-model';
  1746. } else if( model instanceof panels.model.widget ) {
  1747. serial.thingType = 'widget-model';
  1748. }
  1749. // Store this in local storage
  1750. localStorage[ 'panels_clipboard_' + panelsOptions.user ] = JSON.stringify( serial );
  1751. return true;
  1752. },
  1753. /**
  1754. * Check if the current model stored in the clipboard is the expected type
  1755. */
  1756. isModel: function( expected ){
  1757. if( ! this.canCopyPaste() ) {
  1758. return false;
  1759. }
  1760. var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
  1761. if( clipboardObject !== undefined ) {
  1762. clipboardObject = JSON.parse(clipboardObject);
  1763. return clipboardObject.thingType && clipboardObject.thingType === expected;
  1764. }
  1765. return false;
  1766. },
  1767. /**
  1768. * Get the model currently stored in the clipboard
  1769. */
  1770. getModel: function( expected ){
  1771. if( ! this.canCopyPaste() ) {
  1772. return null;
  1773. }
  1774. var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
  1775. if( clipboardObject !== undefined ) {
  1776. clipboardObject = JSON.parse( clipboardObject );
  1777. if( clipboardObject.thingType && clipboardObject.thingType === expected ) {
  1778. return panels.helpers.serialize.unserialize( clipboardObject, clipboardObject.thingType, null );
  1779. }
  1780. }
  1781. return null;
  1782. },
  1783. };
  1784. },{}],12:[function(require,module,exports){
  1785. module.exports = {
  1786. /**
  1787. * Lock window scrolling for the main overlay
  1788. */
  1789. lock: function () {
  1790. if ( jQuery( 'body' ).css( 'overflow' ) === 'hidden' ) {
  1791. return;
  1792. }
  1793. // lock scroll position, but retain settings for later
  1794. var scrollPosition = [
  1795. self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
  1796. self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
  1797. ];
  1798. jQuery( 'body' )
  1799. .data( {
  1800. 'scroll-position': scrollPosition
  1801. } )
  1802. .css( 'overflow', 'hidden' );
  1803. if( ! _.isUndefined( scrollPosition ) ) {
  1804. window.scrollTo( scrollPosition[0], scrollPosition[1] );
  1805. }
  1806. },
  1807. /**
  1808. * Unlock window scrolling
  1809. */
  1810. unlock: function () {
  1811. if ( jQuery( 'body' ).css( 'overflow' ) !== 'hidden' ) {
  1812. return;
  1813. }
  1814. // Check that there are no more dialogs or a live editor
  1815. if ( ! jQuery( '.so-panels-dialog-wrapper' ).is( ':visible' ) && ! jQuery( '.so-panels-live-editor' ).is( ':visible' ) ) {
  1816. jQuery( 'body' ).css( 'overflow', 'visible' );
  1817. var scrollPosition = jQuery( 'body' ).data( 'scroll-position' );
  1818. if( ! _.isUndefined( scrollPosition ) ) {
  1819. window.scrollTo( scrollPosition[0], scrollPosition[1] );
  1820. }
  1821. }
  1822. },
  1823. };
  1824. },{}],13:[function(require,module,exports){
  1825. /*
  1826. This is a modified version of https://github.com/underdogio/backbone-serialize/
  1827. */
  1828. /* global Backbone, module, panels */
  1829. module.exports = {
  1830. serialize: function( thing ){
  1831. var val;
  1832. if( thing instanceof Backbone.Model ) {
  1833. var retObj = {};
  1834. for ( var key in thing.attributes ) {
  1835. if (thing.attributes.hasOwnProperty( key ) ) {
  1836. // Skip these to avoid recursion
  1837. if( key === 'builder' || key === 'collection' ) { continue; }
  1838. // If the value is a Model or a Collection, then serialize them as well
  1839. val = thing.attributes[key];
  1840. if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
  1841. retObj[key] = this.serialize( val );
  1842. } else {
  1843. // Otherwise, save the original value
  1844. retObj[key] = val;
  1845. }
  1846. }
  1847. }
  1848. return retObj;
  1849. }
  1850. else if( thing instanceof Backbone.Collection ) {
  1851. // Walk over all of our models
  1852. var retArr = [];
  1853. for ( var i = 0; i < thing.models.length; i++ ) {
  1854. // If the model is serializable, then serialize it
  1855. val = thing.models[i];
  1856. if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
  1857. retArr.push( this.serialize( val ) );
  1858. } else {
  1859. // Otherwise (it is an object), return it in its current form
  1860. retArr.push( val );
  1861. }
  1862. }
  1863. // Return the serialized models
  1864. return retArr;
  1865. }
  1866. },
  1867. unserialize: function( thing, thingType, parent ) {
  1868. var retObj;
  1869. switch( thingType ) {
  1870. case 'row-model' :
  1871. retObj = new panels.model.row();
  1872. retObj.builder = parent;
  1873. retObj.set( 'style', thing.style );
  1874. retObj.setCells( this.unserialize( thing.cells, 'cell-collection', retObj ) );
  1875. break;
  1876. case 'cell-model' :
  1877. retObj = new panels.model.cell();
  1878. retObj.row = parent;
  1879. retObj.set( 'weight', thing.weight );
  1880. retObj.set( 'style', thing.style );
  1881. retObj.set( 'widgets', this.unserialize( thing.widgets, 'widget-collection', retObj ) );
  1882. break;
  1883. case 'widget-model' :
  1884. retObj = new panels.model.widget();
  1885. retObj.cell = parent;
  1886. for ( var key in thing ) {
  1887. if ( thing.hasOwnProperty( key ) ) {
  1888. retObj.set( key, thing[key] );
  1889. }
  1890. }
  1891. retObj.set( 'widget_id', panels.helpers.utils.generateUUID() );
  1892. break;
  1893. case 'cell-collection':
  1894. retObj = new panels.collection.cells();
  1895. for( var i = 0; i < thing.length; i++ ) {
  1896. retObj.push( this.unserialize( thing[i], 'cell-model', parent ) );
  1897. }
  1898. break;
  1899. case 'widget-collection':
  1900. retObj = new panels.collection.widgets();
  1901. for( var i = 0; i < thing.length; i++ ) {
  1902. retObj.push( this.unserialize( thing[i], 'widget-model', parent ) );
  1903. }
  1904. break;
  1905. default:
  1906. console.log( 'Unknown Thing - ' + thingType );
  1907. break;
  1908. }
  1909. return retObj;
  1910. }
  1911. };
  1912. },{}],14:[function(require,module,exports){
  1913. module.exports = {
  1914. generateUUID: function(){
  1915. var d = new Date().getTime();
  1916. if( window.performance && typeof window.performance.now === "function" ){
  1917. d += performance.now(); //use high-precision timer if available
  1918. }
  1919. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function(c) {
  1920. var r = (d + Math.random()*16)%16 | 0;
  1921. d = Math.floor(d/16);
  1922. return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16);
  1923. } );
  1924. return uuid;
  1925. },
  1926. processTemplate: function ( s ) {
  1927. if ( _.isUndefined( s ) || _.isNull( s ) ) {
  1928. return '';
  1929. }
  1930. s = s.replace( /{{%/g, '<%' );
  1931. s = s.replace( /%}}/g, '%>' );
  1932. s = s.trim();
  1933. return s;
  1934. },
  1935. // From this SO post: http://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element
  1936. selectElementContents: function( element ) {
  1937. var range = document.createRange();
  1938. range.selectNodeContents( element );
  1939. var sel = window.getSelection();
  1940. sel.removeAllRanges();
  1941. sel.addRange( range );
  1942. },
  1943. }
  1944. },{}],15:[function(require,module,exports){
  1945. /* global _, jQuery, panels */
  1946. var panels = window.panels, $ = jQuery;
  1947. module.exports = function ( config ) {
  1948. return this.each( function () {
  1949. var $$ = jQuery( this );
  1950. var widgetId = $$.closest( 'form' ).find( '.widget-id' ).val();
  1951. // Create a config for this specific widget
  1952. var thisConfig = $.extend(true, {}, config);
  1953. // Exit if this isn't a real widget
  1954. if ( ! _.isUndefined( widgetId ) && widgetId.indexOf( '__i__' ) > - 1 ) {
  1955. return;
  1956. }
  1957. // Create the main builder model
  1958. var builderModel = new panels.model.builder();
  1959. // Now for the view to display the builder
  1960. var builderView = new panels.view.builder( {
  1961. model: builderModel,
  1962. config: thisConfig
  1963. } );
  1964. // Save panels data when we close the dialog, if we're in a dialog
  1965. var dialog = $$.closest( '.so-panels-dialog-wrapper' ).data( 'view' );
  1966. if ( ! _.isUndefined( dialog ) ) {
  1967. dialog.on( 'close_dialog', function () {
  1968. builderModel.refreshPanelsData();
  1969. } );
  1970. dialog.on( 'open_dialog_complete', function () {
  1971. // Make sure the new layout widget is always properly setup
  1972. builderView.trigger( 'builder_resize' );
  1973. } );
  1974. dialog.model.on( 'destroy', function () {
  1975. // Destroy the builder
  1976. builderModel.emptyRows().destroy();
  1977. } );
  1978. // Set the parent for all the sub dialogs
  1979. builderView.setDialogParents( panelsOptions.loc.layout_widget, dialog );
  1980. }
  1981. // Basic setup for the builder
  1982. var isWidget = Boolean( $$.closest( '.widget-content' ).length );
  1983. builderView
  1984. .render()
  1985. .attach( {
  1986. container: $$,
  1987. dialog: isWidget || $$.data('mode') === 'dialog',
  1988. type: $$.data( 'type' )
  1989. } )
  1990. .setDataField( $$.find( 'input.panels-data' ) );
  1991. if ( isWidget || $$.data('mode') === 'dialog' ) {
  1992. // Set up the dialog opening
  1993. builderView.setDialogParents( panelsOptions.loc.layout_widget, builderView.dialog );
  1994. $$.find( '.siteorigin-panels-display-builder' ).click( function ( e ) {
  1995. e.preventDefault();
  1996. builderView.dialog.openDialog();
  1997. } );
  1998. } else {
  1999. // Remove the dialog opener button, this is already being displayed in a page builder dialog.
  2000. $$.find( '.siteorigin-panels-display-builder' ).parent().remove();
  2001. }
  2002. // Trigger a global jQuery event after we've setup the builder view
  2003. $( document ).trigger( 'panels_setup', builderView );
  2004. } );
  2005. };
  2006. },{}],16:[function(require,module,exports){
  2007. /**
  2008. * Everything we need for SiteOrigin Page Builder.
  2009. *
  2010. * @copyright Greg Priday 2013 - 2016 - <https://siteorigin.com/>
  2011. * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html
  2012. */
  2013. /* global Backbone, _, jQuery, tinyMCE, panelsOptions, plupload, confirm, console, require */
  2014. var panels = {};
  2015. // Store everything globally
  2016. window.panels = panels;
  2017. window.siteoriginPanels = panels;
  2018. // Helpers
  2019. panels.helpers = {};
  2020. panels.helpers.clipboard = require( './helpers/clipboard' );
  2021. panels.helpers.utils = require( './helpers/utils' );
  2022. panels.helpers.serialize = require( './helpers/serialize' );
  2023. panels.helpers.pageScroll = require( './helpers/page-scroll' );
  2024. // The models
  2025. panels.model = {};
  2026. panels.model.widget = require( './model/widget' );
  2027. panels.model.cell = require( './model/cell' );
  2028. panels.model.row = require( './model/row' );
  2029. panels.model.builder = require( './model/builder' );
  2030. panels.model.historyEntry = require( './model/history-entry' );
  2031. // The collections
  2032. panels.collection = {};
  2033. panels.collection.widgets = require( './collection/widgets' );
  2034. panels.collection.cells = require( './collection/cells' );
  2035. panels.collection.rows = require( './collection/rows' );
  2036. panels.collection.historyEntries = require( './collection/history-entries' );
  2037. // The views
  2038. panels.view = {};
  2039. panels.view.widget = require( './view/widget' );
  2040. panels.view.cell = require( './view/cell' );
  2041. panels.view.row = require( './view/row' );
  2042. panels.view.builder = require( './view/builder' );
  2043. panels.view.dialog = require( './view/dialog' );
  2044. panels.view.styles = require( './view/styles' );
  2045. panels.view.liveEditor = require( './view/live-editor' );
  2046. // The dialogs
  2047. panels.dialog = {};
  2048. panels.dialog.builder = require( './dialog/builder' );
  2049. panels.dialog.widgets = require( './dialog/widgets' );
  2050. panels.dialog.widget = require( './dialog/widget' );
  2051. panels.dialog.prebuilt = require( './dialog/prebuilt' );
  2052. panels.dialog.row = require( './dialog/row' );
  2053. panels.dialog.history = require( './dialog/history' );
  2054. // The utils
  2055. panels.utils = {};
  2056. panels.utils.menu = require( './utils/menu' );
  2057. // jQuery Plugins
  2058. jQuery.fn.soPanelsSetupBuilderWidget = require( './jquery/setup-builder-widget' );
  2059. // Set up Page Builder if we're on the main interface
  2060. jQuery( function ( $ ) {
  2061. var container,
  2062. field,
  2063. form,
  2064. builderConfig;
  2065. var $panelsMetabox = $( '#siteorigin-panels-metabox' );
  2066. form = $( 'form#post' );
  2067. if ( $panelsMetabox.length && form.length ) {
  2068. // This is usually the case when we're in the post edit interface
  2069. container = $panelsMetabox;
  2070. field = $panelsMetabox.find( '.siteorigin-panels-data-field' );
  2071. builderConfig = {
  2072. editorType: 'tinyMCE',
  2073. postId: $( '#post_ID' ).val(),
  2074. editorId: '#content',
  2075. builderType: $panelsMetabox.data( 'builder-type' ),
  2076. builderSupports: $panelsMetabox.data( 'builder-supports' ),
  2077. loadOnAttach: panelsOptions.loadOnAttach && $( '#auto_draft' ).val() == 1,
  2078. loadLiveEditor: $panelsMetabox.data('live-editor') == 1,
  2079. liveEditorPreview: container.data('preview-url')
  2080. };
  2081. }
  2082. else if ( $( '.siteorigin-panels-builder-form' ).length ) {
  2083. // We're dealing with another interface like the custom home page interface
  2084. var $$ = $( '.siteorigin-panels-builder-form' );
  2085. container = $$.find( '.siteorigin-panels-builder-container' );
  2086. field = $$.find( 'input[name="panels_data"]' );
  2087. form = $$;
  2088. builderConfig = {
  2089. editorType: 'standalone',
  2090. postId: $$.data( 'post-id' ),
  2091. editorId: '#post_content',
  2092. builderType: $$.data( 'type' ),
  2093. builderSupports: $$.data( 'builder-supports' ),
  2094. loadLiveEditor: false,
  2095. liveEditorPreview: $$.data( 'preview-url' )
  2096. };
  2097. }
  2098. if ( ! _.isUndefined( container ) ) {
  2099. // If we have a container, then set up the main builder
  2100. var panels = window.siteoriginPanels;
  2101. // Create the main builder model
  2102. var builderModel = new panels.model.builder();
  2103. // Now for the view to display the builder
  2104. var builderView = new panels.view.builder( {
  2105. model: builderModel,
  2106. config: builderConfig
  2107. } );
  2108. // Set up the builder view
  2109. builderView
  2110. .render()
  2111. .attach( {
  2112. container: container
  2113. } )
  2114. .setDataField( field )
  2115. .attachToEditor();
  2116. // When the form is submitted, update the panels data
  2117. form.submit( function () {
  2118. // Refresh the data
  2119. builderModel.refreshPanelsData();
  2120. } );
  2121. container.removeClass( 'so-panels-loading' );
  2122. // Trigger a global jQuery event after we've setup the builder view. Everything is accessible form there
  2123. $( document ).trigger( 'panels_setup', builderView, window.panels );
  2124. }
  2125. // Setup new widgets when they're added in the standard widget interface
  2126. $( document ).on( 'widget-added', function ( e, widget ) {
  2127. $( widget ).find( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
  2128. } );
  2129. // Setup existing widgets on the page (for the widgets interface)
  2130. if ( ! $( 'body' ).hasClass( 'wp-customizer' ) ) {
  2131. $( function () {
  2132. $( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
  2133. } );
  2134. }
  2135. } );
  2136. },{"./collection/cells":1,"./collection/history-entries":2,"./collection/rows":3,"./collection/widgets":4,"./dialog/builder":5,"./dialog/history":6,"./dialog/prebuilt":7,"./dialog/row":8,"./dialog/widget":9,"./dialog/widgets":10,"./helpers/clipboard":11,"./helpers/page-scroll":12,"./helpers/serialize":13,"./helpers/utils":14,"./jquery/setup-builder-widget":15,"./model/builder":17,"./model/cell":18,"./model/history-entry":19,"./model/row":20,"./model/widget":21,"./utils/menu":22,"./view/builder":23,"./view/cell":24,"./view/dialog":25,"./view/live-editor":26,"./view/row":27,"./view/styles":28,"./view/widget":29}],17:[function(require,module,exports){
  2137. module.exports = Backbone.Model.extend({
  2138. layoutPosition: {
  2139. BEFORE: 'before',
  2140. AFTER: 'after',
  2141. REPLACE: 'replace',
  2142. },
  2143. rows: {},
  2144. defaults: {
  2145. 'data': {
  2146. 'widgets': [],
  2147. 'grids': [],
  2148. 'grid_cells': []
  2149. }
  2150. },
  2151. initialize: function () {
  2152. // These are the main rows in the interface
  2153. this.set( 'rows', new panels.collection.rows() );
  2154. },
  2155. /**
  2156. * Add a new row to this builder.
  2157. *
  2158. * @param attrs
  2159. * @param cells
  2160. * @param options
  2161. */
  2162. addRow: function (attrs, cells, options) {
  2163. options = _.extend({
  2164. noAnimate: false
  2165. }, options);
  2166. var cellCollection = new panels.collection.cells(cells);
  2167. attrs = _.extend({
  2168. collection: this.get('rows'),
  2169. cells: cellCollection,
  2170. }, attrs);
  2171. // Create the actual row
  2172. var row = new panels.model.row(attrs);
  2173. row.builder = this;
  2174. this.get('rows').add( row, options );
  2175. return row;
  2176. },
  2177. /**
  2178. * Load the panels data into the builder
  2179. *
  2180. * @param data Object the layout and widgets data to load.
  2181. * @param position string Where to place the new layout. Allowed options are 'before', 'after'. Anything else will
  2182. * cause the new layout to replace the old one.
  2183. */
  2184. loadPanelsData: function ( data, position ) {
  2185. try {
  2186. if ( position === this.layoutPosition.BEFORE ) {
  2187. data = this.concatPanelsData( data, this.getPanelsData() );
  2188. } else if ( position === this.layoutPosition.AFTER ) {
  2189. data = this.concatPanelsData( this.getPanelsData(), data );
  2190. }
  2191. // Start by destroying any rows that currently exist. This will in turn destroy cells, widgets and all the associated views
  2192. this.emptyRows();
  2193. // This will empty out the current rows and reload the builder data.
  2194. this.set( 'data', JSON.parse( JSON.stringify( data ) ), {silent: true} );
  2195. var cit = 0;
  2196. var rows = [];
  2197. if ( _.isUndefined( data.grid_cells ) ) {
  2198. this.trigger( 'load_panels_data' );
  2199. return;
  2200. }
  2201. var gi;
  2202. for ( var ci = 0; ci < data.grid_cells.length; ci ++ ) {
  2203. gi = parseInt( data.grid_cells[ci].grid );
  2204. if ( _.isUndefined( rows[gi] ) ) {
  2205. rows[gi] = [];
  2206. }
  2207. rows[gi].push( data.grid_cells[ci] );
  2208. }
  2209. var builderModel = this;
  2210. _.each( rows, function ( row, i ) {
  2211. var rowAttrs = {};
  2212. if ( ! _.isUndefined( data.grids[i].style ) ) {
  2213. rowAttrs.style = data.grids[i].style;
  2214. }
  2215. if ( ! _.isUndefined( data.grids[i].ratio) ) {
  2216. rowAttrs.ratio = data.grids[i].ratio;
  2217. }
  2218. if ( ! _.isUndefined( data.grids[i].ratio_direction) ) {
  2219. rowAttrs.ratio_direction = data.grids[i].ratio_direction
  2220. }
  2221. if ( ! _.isUndefined( data.grids[i].color_label) ) {
  2222. rowAttrs.color_label = data.grids[i].color_label;
  2223. }
  2224. if ( ! _.isUndefined( data.grids[i].label) ) {
  2225. rowAttrs.label = data.grids[i].label;
  2226. }
  2227. // This will create and add the row model and its cells
  2228. builderModel.addRow(rowAttrs, row, {noAnimate: true} );
  2229. } );
  2230. if ( _.isUndefined( data.widgets ) ) {
  2231. return;
  2232. }
  2233. // Add the widgets
  2234. _.each( data.widgets, function ( widgetData ) {
  2235. var panels_info = null;
  2236. if ( ! _.isUndefined( widgetData.panels_info ) ) {
  2237. panels_info = widgetData.panels_info;
  2238. delete widgetData.panels_info;
  2239. } else {
  2240. panels_info = widgetData.info;
  2241. delete widgetData.info;
  2242. }
  2243. var row = builderModel.get('rows').at( parseInt( panels_info.grid ) );
  2244. var cell = row.get('cells').at( parseInt( panels_info.cell ) );
  2245. var newWidget = new panels.model.widget( {
  2246. class: panels_info.class,
  2247. values: widgetData
  2248. } );
  2249. if ( ! _.isUndefined( panels_info.style ) ) {
  2250. newWidget.set( 'style', panels_info.style );
  2251. }
  2252. if ( ! _.isUndefined( panels_info.read_only ) ) {
  2253. newWidget.set( 'read_only', panels_info.read_only );
  2254. }
  2255. if ( ! _.isUndefined( panels_info.widget_id ) ) {
  2256. newWidget.set( 'widget_id', panels_info.widget_id );
  2257. }
  2258. else {
  2259. newWidget.set( 'widget_id', panels.helpers.utils.generateUUID() );
  2260. }
  2261. if ( ! _.isUndefined( panels_info.label ) ) {
  2262. newWidget.set( 'label', panels_info.label );
  2263. }
  2264. newWidget.cell = cell;
  2265. cell.get('widgets').add( newWidget, { noAnimate: true } );
  2266. } );
  2267. this.trigger( 'load_panels_data' );
  2268. }
  2269. catch ( err ) {
  2270. console.log( 'Error loading data: ' + err.message );
  2271. }
  2272. },
  2273. /**
  2274. * Concatenate the second set of Page Builder data to the first. There is some validation of input, but for the most
  2275. * part it's up to the caller to ensure the Page Builder data is well formed.
  2276. */
  2277. concatPanelsData: function ( panelsDataA, panelsDataB ) {
  2278. if ( _.isUndefined( panelsDataB ) || _.isUndefined( panelsDataB.grids ) || _.isEmpty( panelsDataB.grids ) ||
  2279. _.isUndefined( panelsDataB.grid_cells ) || _.isEmpty( panelsDataB.grid_cells ) ) {
  2280. return panelsDataA;
  2281. }
  2282. if ( _.isUndefined( panelsDataA ) || _.isUndefined( panelsDataA.grids ) || _.isEmpty( panelsDataA.grids ) ) {
  2283. return panelsDataB;
  2284. }
  2285. var gridsBOffset = panelsDataA.grids.length;
  2286. var widgetsBOffset = ! _.isUndefined( panelsDataA.widgets ) ? panelsDataA.widgets.length : 0;
  2287. var newPanelsData = {grids: [], 'grid_cells': [], 'widgets': []};
  2288. // Concatenate grids (rows)
  2289. newPanelsData.grids = panelsDataA.grids.concat( panelsDataB.grids );
  2290. // Create a copy of panelsDataA grid_cells and widgets
  2291. if ( ! _.isUndefined( panelsDataA.grid_cells ) ) {
  2292. newPanelsData.grid_cells = panelsDataA.grid_cells.slice();
  2293. }
  2294. if ( ! _.isUndefined( panelsDataA.widgets ) ) {
  2295. newPanelsData.widgets = panelsDataA.widgets.slice();
  2296. }
  2297. var i;
  2298. // Concatenate grid cells (row columns)
  2299. for ( i = 0; i < panelsDataB.grid_cells.length; i ++ ) {
  2300. var gridCellB = panelsDataB.grid_cells[i];
  2301. gridCellB.grid = parseInt( gridCellB.grid ) + gridsBOffset;
  2302. newPanelsData.grid_cells.push( gridCellB );
  2303. }
  2304. // Concatenate widgets
  2305. if ( ! _.isUndefined( panelsDataB.widgets ) ) {
  2306. for ( i = 0; i < panelsDataB.widgets.length; i ++ ) {
  2307. var widgetB = panelsDataB.widgets[i];
  2308. widgetB.panels_info.grid = parseInt( widgetB.panels_info.grid ) + gridsBOffset;
  2309. widgetB.panels_info.id = parseInt( widgetB.panels_info.id ) + widgetsBOffset;
  2310. newPanelsData.widgets.push( widgetB );
  2311. }
  2312. }
  2313. return newPanelsData;
  2314. },
  2315. /**
  2316. * Convert the content of the builder into a object that represents the page builder data
  2317. */
  2318. getPanelsData: function () {
  2319. var builder = this;
  2320. var data = {
  2321. 'widgets': [],
  2322. 'grids': [],
  2323. 'grid_cells': []
  2324. };
  2325. var widgetId = 0;
  2326. this.get('rows').each( function ( row, ri ) {
  2327. row.get('cells').each( function ( cell, ci ) {
  2328. cell.get('widgets').each( function ( widget, wi ) {
  2329. // Add the data for the widget, including the panels_info field.
  2330. var panels_info = {
  2331. class: widget.get( 'class' ),
  2332. raw: widget.get( 'raw' ),
  2333. grid: ri,
  2334. cell: ci,
  2335. // Strictly this should be an index
  2336. id: widgetId ++,
  2337. widget_id: widget.get( 'widget_id' ),
  2338. style: widget.get( 'style' ),
  2339. label: widget.get( 'label' ),
  2340. };
  2341. if( _.isEmpty( panels_info.widget_id ) ) {
  2342. panels_info.widget_id = panels.helpers.utils.generateUUID();
  2343. }
  2344. var values = _.extend( _.clone( widget.get( 'values' ) ), {
  2345. panels_info: panels_info
  2346. } );
  2347. data.widgets.push( values );
  2348. } );
  2349. // Add the cell info
  2350. data.grid_cells.push( {
  2351. grid: ri,
  2352. index: ci,
  2353. weight: cell.get( 'weight' ),
  2354. style: cell.get( 'style' ),
  2355. } );
  2356. } );
  2357. data.grids.push( {
  2358. cells: row.get('cells').length,
  2359. style: row.get( 'style' ),
  2360. ratio: row.get('ratio'),
  2361. ratio_direction: row.get('ratio_direction'),
  2362. color_label: row.get( 'color_label' ),
  2363. label: row.get( 'label' ),
  2364. } );
  2365. } );
  2366. return data;
  2367. },
  2368. /**
  2369. * This will check all the current entries and refresh the panels data
  2370. */
  2371. refreshPanelsData: function ( args ) {
  2372. args = _.extend( {
  2373. silent: false
  2374. }, args );
  2375. var oldData = this.get( 'data' );
  2376. var newData = this.getPanelsData();
  2377. this.set( 'data', newData, {silent: true} );
  2378. if ( ! args.silent && JSON.stringify( newData ) !== JSON.stringify( oldData ) ) {
  2379. // The default change event doesn't trigger on deep changes, so we'll trigger our own
  2380. this.trigger( 'change' );
  2381. this.trigger( 'change:data' );
  2382. this.trigger( 'refresh_panels_data', newData, args );
  2383. }
  2384. },
  2385. /**
  2386. * Empty all the rows and the cells/widgets they contain.
  2387. */
  2388. emptyRows: function () {
  2389. _.invoke( this.get('rows').toArray(), 'destroy' );
  2390. this.get('rows').reset();
  2391. return this;
  2392. },
  2393. isValidLayoutPosition: function ( position ) {
  2394. return position === this.layoutPosition.BEFORE ||
  2395. position === this.layoutPosition.AFTER ||
  2396. position === this.layoutPosition.REPLACE;
  2397. },
  2398. /**
  2399. * Convert HTML into Panels Data
  2400. * @param html
  2401. */
  2402. getPanelsDataFromHtml: function( html, editorClass ){
  2403. var thisModel = this;
  2404. var $html = jQuery( '<div id="wrapper">' + html + '</div>' );
  2405. if( $html.find('.panel-layout .panel-grid').length ) {
  2406. // This looks like Page Builder html, lets try parse it
  2407. var panels_data = {
  2408. grids: [],
  2409. grid_cells: [],
  2410. widgets: [],
  2411. };
  2412. // The Regex object that'll match SiteOrigin widgets
  2413. var re = new RegExp( panelsOptions.siteoriginWidgetRegex , "i" );
  2414. var decodeEntities = (function() {
  2415. // this prevents any overhead from creating the object each time
  2416. var element = document.createElement('div');
  2417. function decodeHTMLEntities (str) {
  2418. if(str && typeof str === 'string') {
  2419. // strip script/html tags
  2420. str = str.replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '');
  2421. str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');
  2422. element.innerHTML = str;
  2423. str = element.textContent;
  2424. element.textContent = '';
  2425. }
  2426. return str;
  2427. }
  2428. return decodeHTMLEntities;
  2429. })();
  2430. // Remove all wrapping divs from a widget to get its html
  2431. var getTextWidgetContents = function( $el ){
  2432. var $divs = $el.find( 'div' );
  2433. if( ! $divs.length ) {
  2434. return $el.html();
  2435. }
  2436. var i;
  2437. for( i = 0; i < $divs.length - 1; i++ ) {
  2438. if( jQuery.trim( $divs.eq(i).text() ) != jQuery.trim( $divs.eq(i+1).text() ) ) {
  2439. break;
  2440. }
  2441. }
  2442. var title = $divs.eq( i ).find( '.widget-title:header' ),
  2443. titleText = '';
  2444. if( title.length ) {
  2445. titleText = title.html();
  2446. title.remove();
  2447. }
  2448. return {
  2449. title: titleText,
  2450. text: $divs.eq(i).html(),
  2451. };
  2452. };
  2453. var $layout = $html.find( '.panel-layout' ).eq(0);
  2454. var filterNestedLayout = function( i, el ){
  2455. return jQuery( el ).closest( '.panel-layout' ).is( $layout );
  2456. };
  2457. $html.find('> .panel-layout > .panel-grid').filter( filterNestedLayout ).each( function( ri, el ){
  2458. var $row = jQuery( el ),
  2459. $cells = $row.find( '.panel-grid-cell' ).filter( filterNestedLayout );
  2460. panels_data.grids.push( {
  2461. cells: $cells.length,
  2462. style: $row.data( 'style' ),
  2463. ratio: $row.data( 'ratio' ),
  2464. ratio_direction: $row.data( 'ratio-direction' ),
  2465. color_label: $row.data( 'color-label' ),
  2466. label: $row.data( 'label' ),
  2467. } );
  2468. $cells.each( function( ci, el ){
  2469. var $cell = jQuery( el ),
  2470. $widgets = $cell.find( '.so-panel' ).filter( filterNestedLayout );
  2471. panels_data.grid_cells.push( {
  2472. grid: ri,
  2473. weight: ! _.isUndefined( $cell.data( 'weight' ) ) ? parseFloat( $cell.data( 'weight' ) ) : 1,
  2474. style: $cell.data( 'style' ),
  2475. } );
  2476. $widgets.each( function( wi, el ){
  2477. var $widget = jQuery(el),
  2478. widgetContent = $widget.find('.panel-widget-style').length ? $widget.find('.panel-widget-style').html() : $widget.html(),
  2479. panels_info = {
  2480. grid: ri,
  2481. cell: ci,
  2482. style: $widget.data( 'style' ),
  2483. raw: false,
  2484. label: $widget.data( 'label' )
  2485. };
  2486. widgetContent = widgetContent.trim();
  2487. // Check if this is a SiteOrigin Widget
  2488. var match = re.exec( widgetContent );
  2489. if( ! _.isNull( match ) && widgetContent.replace( re, '' ).trim() === '' ) {
  2490. try {
  2491. var classMatch = /class="(.*?)"/.exec( match[3] ),
  2492. dataInput = jQuery( match[5] ),
  2493. data = JSON.parse( decodeEntities( dataInput.val( ) ) ),
  2494. newWidget = data.instance;
  2495. panels_info.class = classMatch[1].replace( /\\\\+/g, '\\' );
  2496. panels_info.raw = false;
  2497. newWidget.panels_info = panels_info;
  2498. panels_data.widgets.push( newWidget );
  2499. }
  2500. catch ( err ) {
  2501. // There was a problem, so treat this as a standard editor widget
  2502. panels_info.class = editorClass;
  2503. panels_data.widgets.push( _.extend( getTextWidgetContents( $widget ), {
  2504. filter: "1",
  2505. type: "visual",
  2506. panels_info: panels_info
  2507. } ) );
  2508. }
  2509. // Continue
  2510. return true;
  2511. }
  2512. else if( widgetContent.indexOf( 'panel-layout' ) !== -1 ) {
  2513. // Check if this is a layout widget
  2514. var $widgetContent = jQuery( '<div>' + widgetContent + '</div>' );
  2515. if( $widgetContent.find('.panel-layout .panel-grid').length ) {
  2516. // This is a standard editor class widget
  2517. panels_info.class = 'SiteOrigin_Panels_Widgets_Layout';
  2518. panels_data.widgets.push( {
  2519. panels_data: thisModel.getPanelsDataFromHtml( widgetContent, editorClass ),
  2520. panels_info: panels_info
  2521. } );
  2522. // continue
  2523. return true;
  2524. }
  2525. }
  2526. // This is a standard editor class widget
  2527. panels_info.class = editorClass;
  2528. panels_data.widgets.push( _.extend( getTextWidgetContents( $widget ), {
  2529. filter: "1",
  2530. type: "visual",
  2531. panels_info: panels_info
  2532. } ) );
  2533. return true;
  2534. } );
  2535. } );
  2536. } );
  2537. // Remove all the Page Builder content
  2538. $html.find('.panel-layout').remove();
  2539. $html.find('style[data-panels-style-for-post]').remove();
  2540. // If there's anything left, add it to an editor widget at the end of panels_data
  2541. if( $html.html().replace(/^\s+|\s+$/gm,'').length ) {
  2542. panels_data.grids.push( {
  2543. cells: 1,
  2544. style: {},
  2545. } );
  2546. panels_data.grid_cells.push( {
  2547. grid: panels_data.grids.length - 1,
  2548. weight: 1,
  2549. } );
  2550. panels_data.widgets.push( {
  2551. filter: "1",
  2552. text: $html.html().replace(/^\s+|\s+$/gm,''),
  2553. title: "",
  2554. type: "visual",
  2555. panels_info: {
  2556. class: editorClass,
  2557. raw: false,
  2558. grid: panels_data.grids.length - 1,
  2559. cell: 0
  2560. }
  2561. } );
  2562. }
  2563. return panels_data;
  2564. }
  2565. else {
  2566. // This is probably just old school post content
  2567. return {
  2568. grid_cells: [ { grid: 0, weight: 1 } ],
  2569. grids: [ { cells: 1 } ],
  2570. widgets: [
  2571. {
  2572. filter: "1",
  2573. text: html,
  2574. title: "",
  2575. type: "visual",
  2576. panels_info: {
  2577. class: editorClass,
  2578. raw: false,
  2579. grid: 0,
  2580. cell: 0
  2581. }
  2582. }
  2583. ]
  2584. };
  2585. }
  2586. }
  2587. } );
  2588. },{}],18:[function(require,module,exports){
  2589. module.exports = Backbone.Model.extend( {
  2590. /* A collection of widgets */
  2591. widgets: {},
  2592. /* The row this model belongs to */
  2593. row: null,
  2594. defaults: {
  2595. weight: 0,
  2596. style: {}
  2597. },
  2598. indexes: null,
  2599. /**
  2600. * Set up the cell model
  2601. */
  2602. initialize: function () {
  2603. this.set( 'widgets', new panels.collection.widgets() );
  2604. this.on( 'destroy', this.onDestroy, this );
  2605. },
  2606. /**
  2607. * Triggered when we destroy a cell
  2608. */
  2609. onDestroy: function () {
  2610. // Destroy all the widgets
  2611. _.invoke( this.get('widgets').toArray(), 'destroy' );
  2612. this.get('widgets').reset();
  2613. },
  2614. /**
  2615. * Create a clone of the cell, along with all its widgets
  2616. */
  2617. clone: function ( row, cloneOptions ) {
  2618. if ( _.isUndefined( row ) ) {
  2619. row = this.row;
  2620. }
  2621. cloneOptions = _.extend( {cloneWidgets: true}, cloneOptions );
  2622. var clone = new this.constructor( this.attributes );
  2623. clone.set( 'collection', row.get('cells'), {silent: true} );
  2624. clone.row = row;
  2625. if ( cloneOptions.cloneWidgets ) {
  2626. // Now we're going add all the widgets that belong to this, to the clone
  2627. this.get('widgets').each( function ( widget ) {
  2628. clone.get('widgets').add( widget.clone( clone, cloneOptions ), {silent: true} );
  2629. } );
  2630. }
  2631. return clone;
  2632. }
  2633. } );
  2634. },{}],19:[function(require,module,exports){
  2635. module.exports = Backbone.Model.extend( {
  2636. defaults: {
  2637. text: '',
  2638. data: '',
  2639. time: null,
  2640. count: 1
  2641. }
  2642. } );
  2643. },{}],20:[function(require,module,exports){
  2644. module.exports = Backbone.Model.extend( {
  2645. /* The builder model */
  2646. builder: null,
  2647. defaults: {
  2648. style: {}
  2649. },
  2650. indexes: null,
  2651. /**
  2652. * Initialize the row model
  2653. */
  2654. initialize: function () {
  2655. if ( _.isEmpty(this.get('cells') ) ) {
  2656. this.set('cells', new panels.collection.cells());
  2657. }
  2658. else {
  2659. // Make sure that the cells have this row set as their parent
  2660. this.get('cells').each( function( cell ){
  2661. cell.row = this;
  2662. }.bind( this ) );
  2663. }
  2664. this.on( 'destroy', this.onDestroy, this );
  2665. },
  2666. /**
  2667. * Add cells to the model row
  2668. *
  2669. * @param newCells the updated collection of cell models
  2670. */
  2671. setCells: function ( newCells ) {
  2672. var currentCells = this.get('cells') || new panels.collection.cells();
  2673. var cellsToRemove = [];
  2674. currentCells.each(function (cell, i) {
  2675. var newCell = newCells.at(i);
  2676. if(newCell) {
  2677. cell.set('weight', newCell.get('weight'));
  2678. } else {
  2679. var newParentCell = currentCells.at( newCells.length - 1 );
  2680. // First move all the widgets to the new cell
  2681. var widgetsToMove = cell.get('widgets').models.slice();
  2682. for ( var j = 0; j < widgetsToMove.length; j++ ) {
  2683. widgetsToMove[j].moveToCell( newParentCell, { silent: false } );
  2684. }
  2685. cellsToRemove.push(cell);
  2686. }
  2687. });
  2688. _.each(cellsToRemove, function(cell) {
  2689. currentCells.remove(cell);
  2690. });
  2691. if( newCells.length > currentCells.length) {
  2692. _.each(newCells.slice(currentCells.length, newCells.length), function (newCell) {
  2693. // TODO: make sure row and collection is set correctly when cell is created then we can just add new cells
  2694. newCell.set({collection: currentCells});
  2695. newCell.row = this;
  2696. currentCells.add(newCell);
  2697. }.bind(this));
  2698. }
  2699. // Rescale the cells when we add or remove
  2700. this.reweightCells();
  2701. },
  2702. /**
  2703. * Make sure that all the cell weights add up to 1
  2704. */
  2705. reweightCells: function () {
  2706. var totalWeight = 0;
  2707. var cells = this.get('cells');
  2708. cells.each( function ( cell ) {
  2709. totalWeight += cell.get( 'weight' );
  2710. } );
  2711. cells.each( function ( cell ) {
  2712. cell.set( 'weight', cell.get( 'weight' ) / totalWeight );
  2713. } );
  2714. // This is for the row view to hook into and resize
  2715. this.trigger( 'reweight_cells' );
  2716. },
  2717. /**
  2718. * Triggered when the model is destroyed
  2719. */
  2720. onDestroy: function () {
  2721. // Also destroy all the cells
  2722. _.invoke( this.get('cells').toArray(), 'destroy' );
  2723. this.get('cells').reset();
  2724. },
  2725. /**
  2726. * Create a clone of the row, along with all its cells
  2727. *
  2728. * @param {panels.model.builder} builder The builder model to attach this to.
  2729. *
  2730. * @return {panels.model.row} The cloned row.
  2731. */
  2732. clone: function ( builder ) {
  2733. if ( _.isUndefined( builder ) ) {
  2734. builder = this.builder;
  2735. }
  2736. var clone = new this.constructor( this.attributes );
  2737. clone.set( 'collection', builder.get('rows'), {silent: true} );
  2738. clone.builder = builder;
  2739. var cellClones = new panels.collection.cells();
  2740. this.get('cells').each( function ( cell ) {
  2741. cellClones.add( cell.clone( clone ), {silent: true} );
  2742. } );
  2743. clone.set( 'cells', cellClones );
  2744. return clone;
  2745. }
  2746. } );
  2747. },{}],21:[function(require,module,exports){
  2748. /**
  2749. * Model for an instance of a widget
  2750. */
  2751. module.exports = Backbone.Model.extend( {
  2752. cell: null,
  2753. defaults: {
  2754. // The PHP Class of the widget
  2755. class: null,
  2756. // Is this class missing? Missing widgets are a special case.
  2757. missing: false,
  2758. // The values of the widget
  2759. values: {},
  2760. // Have the current values been passed through the widgets update function
  2761. raw: false,
  2762. // Visual style fields
  2763. style: {},
  2764. read_only: false,
  2765. widget_id: '',
  2766. },
  2767. indexes: null,
  2768. initialize: function () {
  2769. var widgetClass = this.get( 'class' );
  2770. if ( _.isUndefined( panelsOptions.widgets[widgetClass] ) || ! panelsOptions.widgets[widgetClass].installed ) {
  2771. this.set( 'missing', true );
  2772. }
  2773. },
  2774. /**
  2775. * @param field
  2776. * @returns {*}
  2777. */
  2778. getWidgetField: function ( field ) {
  2779. if ( _.isUndefined( panelsOptions.widgets[this.get( 'class' )] ) ) {
  2780. if ( field === 'title' || field === 'description' ) {
  2781. return panelsOptions.loc.missing_widget[field];
  2782. } else {
  2783. return '';
  2784. }
  2785. } else if ( this.has( 'label' ) && ! _.isEmpty( this.get( 'label' ) ) ) {
  2786. // Use the label instead of the actual widget title
  2787. return this.get( 'label' );
  2788. } else {
  2789. return panelsOptions.widgets[ this.get( 'class' ) ][ field ];
  2790. }
  2791. },
  2792. /**
  2793. * Move this widget model to a new cell. Called by the views.
  2794. *
  2795. * @param panels.model.cell newCell
  2796. * @param object options The options passed to the
  2797. *
  2798. * @return boolean Indicating if the widget was moved into a different cell
  2799. */
  2800. moveToCell: function ( newCell, options, at ) {
  2801. options = _.extend( {
  2802. silent: true,
  2803. }, options );
  2804. this.cell = newCell;
  2805. this.collection.remove( this, options );
  2806. newCell.get('widgets').add( this, _.extend( {
  2807. at: at
  2808. }, options ) );
  2809. // This should be used by views to reposition everything.
  2810. this.trigger( 'move_to_cell', newCell, at );
  2811. return this;
  2812. },
  2813. /**
  2814. * Trigger an event on the model that indicates a user wants to edit it
  2815. */
  2816. triggerEdit: function () {
  2817. this.trigger( 'user_edit', this );
  2818. },
  2819. /**
  2820. * Trigger an event on the widget that indicates a user wants to duplicate it
  2821. */
  2822. triggerDuplicate: function () {
  2823. this.trigger( 'user_duplicate', this );
  2824. },
  2825. /**
  2826. * This is basically a wrapper for set that checks if we need to trigger a change
  2827. */
  2828. setValues: function ( values ) {
  2829. var hasChanged = false;
  2830. if ( JSON.stringify( values ) !== JSON.stringify( this.get( 'values' ) ) ) {
  2831. hasChanged = true;
  2832. }
  2833. this.set( 'values', values, {silent: true} );
  2834. if ( hasChanged ) {
  2835. // We'll trigger our own change events.
  2836. // NB: Must include the model being changed (i.e. `this`) as a workaround for a bug in Backbone 1.2.3
  2837. this.trigger( 'change', this );
  2838. this.trigger( 'change:values' );
  2839. }
  2840. },
  2841. /**
  2842. * Create a clone of this widget attached to the given cell.
  2843. *
  2844. * @param {panels.model.cell} cell The cell model we're attaching this widget clone to.
  2845. * @returns {panels.model.widget}
  2846. */
  2847. clone: function ( cell, options ) {
  2848. if ( _.isUndefined( cell ) ) {
  2849. cell = this.cell;
  2850. }
  2851. var clone = new this.constructor( this.attributes );
  2852. // Create a deep clone of the original values
  2853. var cloneValues = JSON.parse( JSON.stringify( this.get( 'values' ) ) );
  2854. // We want to exclude any fields that start with _ from the clone. Assuming these are internal.
  2855. var cleanClone = function ( vals ) {
  2856. _.each( vals, function ( el, i ) {
  2857. if ( _.isString( i ) && i[0] === '_' ) {
  2858. delete vals[i];
  2859. }
  2860. else if ( _.isObject( vals[i] ) ) {
  2861. cleanClone( vals[i] );
  2862. }
  2863. } );
  2864. return vals;
  2865. };
  2866. cloneValues = cleanClone( cloneValues );
  2867. if ( this.get( 'class' ) === "SiteOrigin_Panels_Widgets_Layout" ) {
  2868. // Special case of this being a layout widget, it needs a new ID
  2869. cloneValues.builder_id = Math.random().toString( 36 ).substr( 2 );
  2870. }
  2871. clone.set( 'widget_id', '' );
  2872. clone.set( 'values', cloneValues, {silent: true} );
  2873. clone.set( 'collection', cell.get('widgets'), {silent: true} );
  2874. clone.cell = cell;
  2875. // This is used to force a form reload later on
  2876. clone.isDuplicate = true;
  2877. return clone;
  2878. },
  2879. /**
  2880. * Gets the value that makes most sense as the title.
  2881. */
  2882. getTitle: function () {
  2883. var widgetData = panelsOptions.widgets[this.get( 'class' )];
  2884. if ( _.isUndefined( widgetData ) ) {
  2885. return this.get( 'class' ).replace( /_/g, ' ' );
  2886. }
  2887. else if ( ! _.isUndefined( widgetData.panels_title ) ) {
  2888. // This means that the widget has told us which field it wants us to use as a title
  2889. if ( widgetData.panels_title === false ) {
  2890. return panelsOptions.widgets[this.get( 'class' )].description;
  2891. }
  2892. }
  2893. var values = this.get( 'values' );
  2894. // Create a list of fields to check for a title
  2895. var titleFields = ['title', 'text'];
  2896. for ( var k in values ) {
  2897. if ( values.hasOwnProperty( k ) ) {
  2898. titleFields.push( k );
  2899. }
  2900. }
  2901. titleFields = _.uniq( titleFields );
  2902. for ( var i in titleFields ) {
  2903. if (
  2904. ! _.isUndefined( values[titleFields[i]] ) &&
  2905. _.isString( values[titleFields[i]] ) &&
  2906. values[titleFields[i]] !== '' &&
  2907. values[titleFields[i]] !== 'on' &&
  2908. titleFields[i][0] !== '_' && ! jQuery.isNumeric( values[titleFields[i]] )
  2909. ) {
  2910. var title = values[titleFields[i]];
  2911. title = title.replace( /<\/?[^>]+(>|$)/g, "" );
  2912. var parts = title.split( " " );
  2913. parts = parts.slice( 0, 20 );
  2914. return parts.join( ' ' );
  2915. }
  2916. }
  2917. // If we still have nothing, then just return the widget description
  2918. return this.getWidgetField( 'description' );
  2919. }
  2920. } );
  2921. },{}],22:[function(require,module,exports){
  2922. var panels = window.panels, $ = jQuery;
  2923. module.exports = Backbone.View.extend( {
  2924. wrapperTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-context-menu' ).html() ) ),
  2925. sectionTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-context-menu-section' ).html() ) ),
  2926. contexts: [],
  2927. active: false,
  2928. events: {
  2929. 'keyup .so-search-wrapper input': 'searchKeyUp'
  2930. },
  2931. /**
  2932. * Intialize the context menu
  2933. */
  2934. initialize: function () {
  2935. this.listenContextMenu();
  2936. this.render();
  2937. this.attach();
  2938. },
  2939. /**
  2940. * Listen for the right click context menu
  2941. */
  2942. listenContextMenu: function () {
  2943. var thisView = this;
  2944. $( window ).on( 'contextmenu', function ( e ) {
  2945. if ( thisView.active && ! thisView.isOverEl( thisView.$el, e ) ) {
  2946. thisView.closeMenu();
  2947. thisView.active = false;
  2948. e.preventDefault();
  2949. return false;
  2950. }
  2951. if ( thisView.active ) {
  2952. // Lets not double up on the context menu
  2953. return true;
  2954. }
  2955. // Other components should listen to activate_context
  2956. thisView.active = false;
  2957. thisView.trigger( 'activate_context', e, thisView );
  2958. if ( thisView.active ) {
  2959. // We don't want the default event to happen.
  2960. e.preventDefault();
  2961. thisView.openMenu( {
  2962. left: e.pageX,
  2963. top: e.pageY
  2964. } );
  2965. }
  2966. } );
  2967. },
  2968. render: function () {
  2969. this.setElement( this.wrapperTemplate() );
  2970. },
  2971. attach: function () {
  2972. this.$el.appendTo( 'body' );
  2973. },
  2974. /**
  2975. * Display the actual context menu.
  2976. *
  2977. * @param position
  2978. */
  2979. openMenu: function ( position ) {
  2980. this.trigger( 'open_menu' );
  2981. // Start listening for situations when we should close the menu
  2982. $( window ).on( 'keyup', {menu: this}, this.keyboardListen );
  2983. $( window ).on( 'click', {menu: this}, this.clickOutsideListen );
  2984. // Set the maximum height of the menu
  2985. this.$el.css( 'max-height', $( window ).height() - 20 );
  2986. // Correct the left position
  2987. if ( position.left + this.$el.outerWidth() + 10 >= $( window ).width() ) {
  2988. position.left = $( window ).width() - this.$el.outerWidth() - 10;
  2989. }
  2990. if ( position.left <= 0 ) {
  2991. position.left = 10;
  2992. }
  2993. // Check top position
  2994. if ( position.top + this.$el.outerHeight() - $( window ).scrollTop() + 10 >= $( window ).height() ) {
  2995. position.top = $( window ).height() + $( window ).scrollTop() - this.$el.outerHeight() - 10;
  2996. }
  2997. if ( position.left <= 0 ) {
  2998. position.left = 10;
  2999. }
  3000. // position the contextual menu
  3001. this.$el.css( {
  3002. left: position.left + 1,
  3003. top: position.top + 1
  3004. } ).show();
  3005. this.$( '.so-search-wrapper input' ).focus();
  3006. },
  3007. closeMenu: function () {
  3008. this.trigger( 'close_menu' );
  3009. // Stop listening for situations when we should close the menu
  3010. $( window ).off( 'keyup', this.keyboardListen );
  3011. $( window ).off( 'click', this.clickOutsideListen );
  3012. this.active = false;
  3013. this.$el.empty().hide();
  3014. },
  3015. /**
  3016. * Keyboard events handler
  3017. */
  3018. keyboardListen: function ( e ) {
  3019. var menu = e.data.menu;
  3020. switch ( e.which ) {
  3021. case 27:
  3022. menu.closeMenu();
  3023. break;
  3024. }
  3025. },
  3026. /**
  3027. * Listen for a click outside the menu to close it.
  3028. * @param e
  3029. */
  3030. clickOutsideListen: function ( e ) {
  3031. var menu = e.data.menu;
  3032. if ( e.which !== 3 && menu.$el.is( ':visible' ) && ! menu.isOverEl( menu.$el, e ) ) {
  3033. menu.closeMenu();
  3034. }
  3035. },
  3036. /**
  3037. * Add a new section to the contextual menu.
  3038. *
  3039. * @param settings
  3040. * @param items
  3041. * @param callback
  3042. */
  3043. addSection: function ( id, settings, items, callback ) {
  3044. var thisView = this;
  3045. settings = _.extend( {
  3046. display: 5,
  3047. defaultDisplay: false,
  3048. search: true,
  3049. // All the labels
  3050. sectionTitle: '',
  3051. searchPlaceholder: '',
  3052. // This is the key to be used in items for the title. Makes it easier to list objects
  3053. titleKey: 'title'
  3054. }, settings );
  3055. // Create the new section
  3056. var section = $( this.sectionTemplate( {
  3057. settings: settings,
  3058. items: items
  3059. } ) ).attr( 'id', 'panels-menu-section-' + id );
  3060. this.$el.append( section );
  3061. section.find( '.so-item:not(.so-confirm)' ).click( function () {
  3062. var $$ = $( this );
  3063. callback( $$.data( 'key' ) );
  3064. thisView.closeMenu();
  3065. } );
  3066. section.find( '.so-item.so-confirm' ).click( function () {
  3067. var $$ = $( this );
  3068. if ( $$.hasClass( 'so-confirming' ) ) {
  3069. callback( $$.data( 'key' ) );
  3070. thisView.closeMenu();
  3071. return;
  3072. }
  3073. $$
  3074. .data( 'original-text', $$.html() )
  3075. .addClass( 'so-confirming' )
  3076. .html( '<span class="dashicons dashicons-yes"></span> ' + panelsOptions.loc.dropdown_confirm );
  3077. setTimeout( function () {
  3078. $$.removeClass( 'so-confirming' );
  3079. $$.html( $$.data( 'original-text' ) );
  3080. }, 2500 );
  3081. } );
  3082. section.data( 'settings', settings ).find( '.so-search-wrapper input' ).trigger( 'keyup' );
  3083. this.active = true;
  3084. },
  3085. /**
  3086. * Check if a section exists in the current menu.
  3087. *
  3088. * @param id
  3089. * @returns {boolean}
  3090. */
  3091. hasSection: function( id ){
  3092. return this.$el.find( '#panels-menu-section-' + id ).length > 0;
  3093. },
  3094. /**
  3095. * Handle searching inside a section.
  3096. *
  3097. * @param e
  3098. * @returns {boolean}
  3099. */
  3100. searchKeyUp: function ( e ) {
  3101. var
  3102. $$ = $( e.currentTarget ),
  3103. section = $$.closest( '.so-section' ),
  3104. settings = section.data( 'settings' );
  3105. if ( e.which === 38 || e.which === 40 ) {
  3106. // First, lets check if this is an up, down or enter press
  3107. var
  3108. items = section.find( 'ul li:visible' ),
  3109. activeItem = items.filter( '.so-active' ).eq( 0 );
  3110. if ( activeItem.length ) {
  3111. items.removeClass( 'so-active' );
  3112. var activeIndex = items.index( activeItem );
  3113. if ( e.which === 38 ) {
  3114. if ( activeIndex - 1 < 0 ) {
  3115. activeItem = items.last();
  3116. } else {
  3117. activeItem = items.eq( activeIndex - 1 );
  3118. }
  3119. }
  3120. else if ( e.which === 40 ) {
  3121. if ( activeIndex + 1 >= items.length ) {
  3122. activeItem = items.first();
  3123. } else {
  3124. activeItem = items.eq( activeIndex + 1 );
  3125. }
  3126. }
  3127. }
  3128. else if ( e.which === 38 ) {
  3129. activeItem = items.last();
  3130. }
  3131. else if ( e.which === 40 ) {
  3132. activeItem = items.first();
  3133. }
  3134. activeItem.addClass( 'so-active' );
  3135. return false;
  3136. }
  3137. if ( e.which === 13 ) {
  3138. if ( section.find( 'ul li:visible' ).length === 1 ) {
  3139. // We'll treat a single visible item as active when enter is clicked
  3140. section.find( 'ul li:visible' ).trigger( 'click' );
  3141. return false;
  3142. }
  3143. section.find( 'ul li.so-active:visible' ).trigger( 'click' );
  3144. return false;
  3145. }
  3146. if ( $$.val() === '' ) {
  3147. // We'll display the defaultDisplay items
  3148. if ( settings.defaultDisplay ) {
  3149. section.find( '.so-item' ).hide();
  3150. for ( var i = 0; i < settings.defaultDisplay.length; i ++ ) {
  3151. section.find( '.so-item[data-key="' + settings.defaultDisplay[i] + '"]' ).show();
  3152. }
  3153. } else {
  3154. // We'll just display all the items
  3155. section.find( '.so-item' ).show();
  3156. }
  3157. } else {
  3158. section.find( '.so-item' ).hide().each( function () {
  3159. var item = $( this );
  3160. if ( item.html().toLowerCase().indexOf( $$.val().toLowerCase() ) !== - 1 ) {
  3161. item.show();
  3162. }
  3163. } );
  3164. }
  3165. // Now, we'll only show the first settings.display visible items
  3166. section.find( '.so-item:visible:gt(' + (
  3167. settings.display - 1
  3168. ) + ')' ).hide();
  3169. if ( section.find( '.so-item:visible' ).length === 0 && $$.val() !== '' ) {
  3170. section.find( '.so-no-results' ).show();
  3171. } else {
  3172. section.find( '.so-no-results' ).hide();
  3173. }
  3174. },
  3175. /**
  3176. * Check if the given mouse event is over the element
  3177. * @param el
  3178. * @param event
  3179. */
  3180. isOverEl: function ( el, event ) {
  3181. var elPos = [
  3182. [el.offset().left, el.offset().top],
  3183. [el.offset().left + el.outerWidth(), el.offset().top + el.outerHeight()]
  3184. ];
  3185. // Return if this event is over the given element
  3186. return (
  3187. event.pageX >= elPos[0][0] && event.pageX <= elPos[1][0] &&
  3188. event.pageY >= elPos[0][1] && event.pageY <= elPos[1][1]
  3189. );
  3190. }
  3191. } );
  3192. },{}],23:[function(require,module,exports){
  3193. var panels = window.panels, $ = jQuery;
  3194. module.exports = Backbone.View.extend( {
  3195. // Config options
  3196. config: {},
  3197. template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder' ).html() ) ),
  3198. dialogs: {},
  3199. rowsSortable: null,
  3200. dataField: false,
  3201. currentData: '',
  3202. attachedToEditor: false,
  3203. attachedVisible: false,
  3204. liveEditor: undefined,
  3205. menu: false,
  3206. activeCell: null,
  3207. events: {
  3208. 'click .so-tool-button.so-widget-add': 'displayAddWidgetDialog',
  3209. 'click .so-tool-button.so-row-add': 'displayAddRowDialog',
  3210. 'click .so-tool-button.so-prebuilt-add': 'displayAddPrebuiltDialog',
  3211. 'click .so-tool-button.so-history': 'displayHistoryDialog',
  3212. 'click .so-tool-button.so-live-editor': 'displayLiveEditor'
  3213. },
  3214. /* A row collection */
  3215. rows: null,
  3216. /**
  3217. * Initialize the builder
  3218. */
  3219. initialize: function ( options ) {
  3220. var builder = this;
  3221. this.config = _.extend( {
  3222. loadLiveEditor: false,
  3223. builderSupports : {}
  3224. }, options.config);
  3225. // These are the actions that a user can perform in the builder
  3226. this.config.builderSupports = _.extend( {
  3227. addRow: true,
  3228. editRow: true,
  3229. deleteRow: true,
  3230. moveRow: true,
  3231. addWidget: true,
  3232. editWidget: true,
  3233. deleteWidget: true,
  3234. moveWidget: true,
  3235. prebuilt: true,
  3236. history: true,
  3237. liveEditor: true,
  3238. revertToEditor: true
  3239. }, this.config.builderSupports );
  3240. // Automatically load the live editor as soon as it's ready
  3241. if( options.config.loadLiveEditor ) {
  3242. this.on( 'builder_live_editor_added', function(){
  3243. this.displayLiveEditor();
  3244. } );
  3245. }
  3246. // Now lets create all the dialog boxes that the main builder interface uses
  3247. this.dialogs = {
  3248. widgets: new panels.dialog.widgets(),
  3249. row: new panels.dialog.row(),
  3250. prebuilt: new panels.dialog.prebuilt()
  3251. };
  3252. // Set the builder for each dialog and render it.
  3253. _.each( this.dialogs, function ( p, i, d ) {
  3254. d[i].setBuilder( builder );
  3255. } );
  3256. this.dialogs.row.setRowDialogType( 'create' );
  3257. // This handles a new row being added to the collection - we'll display it in the interface
  3258. this.model.get('rows').on( 'add', this.onAddRow, this );
  3259. // Reflow the entire builder when ever the
  3260. $( window ).resize( function ( e ) {
  3261. if ( e.target === window ) {
  3262. builder.trigger( 'builder_resize' );
  3263. }
  3264. } );
  3265. // When the data changes in the model, store it in the field
  3266. this.model.on( 'change:data load_panels_data', this.storeModelData, this );
  3267. // Handle a content change
  3268. this.on( 'content_change', this.handleContentChange, this );
  3269. this.on( 'display_builder', this.handleDisplayBuilder, this );
  3270. this.on( 'hide_builder', this.handleHideBuilder, this );
  3271. this.on( 'builder_rendered builder_resize', this.handleBuilderSizing, this );
  3272. this.model.on( 'change:data load_panels_data', this.toggleWelcomeDisplay, this );
  3273. this.on( 'display_builder', this.wrapEditorExpandAdjust, this );
  3274. // Create the context menu for this builder
  3275. this.menu = new panels.utils.menu( {} );
  3276. this.menu.on( 'activate_context', this.activateContextMenu, this );
  3277. if( this.config.loadOnAttach ) {
  3278. this.on( 'builder_attached_to_editor', function(){
  3279. this.displayAttachedBuilder( { confirm: false } );
  3280. }, this );
  3281. }
  3282. return this;
  3283. },
  3284. /**
  3285. * Render the builder interface.
  3286. *
  3287. * @return {panels.view.builder}
  3288. */
  3289. render: function () {
  3290. // this.$el.html( this.template() );
  3291. this.setElement( this.template() );
  3292. this.$el
  3293. .attr( 'id', 'siteorigin-panels-builder-' + this.cid )
  3294. .addClass( 'so-builder-container' );
  3295. this.trigger( 'builder_rendered' );
  3296. return this;
  3297. },
  3298. /**
  3299. * Attach the builder to the given container
  3300. *
  3301. * @param container
  3302. * @returns {panels.view.builder}
  3303. */
  3304. attach: function ( options ) {
  3305. options = _.extend( {
  3306. container: false,
  3307. dialog: false
  3308. }, options );
  3309. if ( options.dialog ) {
  3310. // We're going to add this to a dialog
  3311. this.dialog = new panels.dialog.builder();
  3312. this.dialog.builder = this;
  3313. } else {
  3314. // Attach this in the standard way
  3315. this.$el.appendTo( options.container );
  3316. this.metabox = options.container.closest( '.postbox' );
  3317. this.initSortable();
  3318. this.trigger( 'attached_to_container', options.container );
  3319. }
  3320. this.trigger( 'builder_attached' );
  3321. // Add support for components we have
  3322. if( this.supports( 'liveEditor' ) ) {
  3323. this.addLiveEditor();
  3324. }
  3325. if( this.supports( 'history' ) ) {
  3326. this.addHistoryBrowser();
  3327. }
  3328. // Hide toolbar buttons we don't support
  3329. var toolbar = this.$('.so-builder-toolbar');
  3330. if( ! this.supports( 'addWidget' ) ) {
  3331. toolbar.find('.so-widget-add' ).hide();
  3332. }
  3333. if( ! this.supports( 'addRow' ) ) {
  3334. toolbar.find('.so-row-add' ).hide();
  3335. }
  3336. if( ! this.supports( 'prebuilt' ) ) {
  3337. toolbar.find('.so-prebuilt-add' ).hide();
  3338. }
  3339. return this;
  3340. },
  3341. /**
  3342. * This will move the Page Builder meta box into the editor if we're in the post/page edit interface.
  3343. *
  3344. * @returns {panels.view.builder}
  3345. */
  3346. attachToEditor: function () {
  3347. if ( this.config.editorType !== 'tinyMCE' ) {
  3348. return this;
  3349. }
  3350. this.attachedToEditor = true;
  3351. var metabox = this.metabox;
  3352. var thisView = this;
  3353. // Handle switching between the page builder and other tabs
  3354. $( '#wp-content-wrap .wp-editor-tabs' )
  3355. .find( '.wp-switch-editor' )
  3356. .click( function ( e ) {
  3357. e.preventDefault();
  3358. $( '#wp-content-editor-container' ).show();
  3359. // metabox.hide();
  3360. $( '#wp-content-wrap' ).removeClass( 'panels-active' );
  3361. $( '#content-resize-handle' ).show();
  3362. // Make sure the word count is visible
  3363. thisView.trigger( 'hide_builder' );
  3364. } ).end()
  3365. .append(
  3366. $( '<a id="content-panels" class="hide-if-no-js wp-switch-editor switch-panels">' + metabox.find( '.hndle span' ).html() + '</a>' )
  3367. .click( function ( e ) {
  3368. if ( thisView.displayAttachedBuilder( { confirm: true } ) ) {
  3369. e.preventDefault();
  3370. }
  3371. } )
  3372. );
  3373. // Switch back to the standard editor
  3374. if( this.supports( 'revertToEditor' ) ) {
  3375. metabox.find( '.so-switch-to-standard' ).click( function ( e ) {
  3376. e.preventDefault();
  3377. if ( ! confirm( panelsOptions.loc.confirm_stop_builder ) ) {
  3378. return;
  3379. }
  3380. // User is switching to the standard visual editor
  3381. thisView.addHistoryEntry( 'back_to_editor' );
  3382. thisView.model.loadPanelsData( false );
  3383. // Switch back to the standard editor
  3384. $( '#wp-content-wrap' ).show();
  3385. metabox.hide();
  3386. // Resize to trigger reflow of WordPress editor stuff
  3387. $( window ).resize();
  3388. thisView.attachedVisible = false;
  3389. thisView.trigger( 'hide_builder' );
  3390. } ).show();
  3391. }
  3392. // Move the panels box into a tab of the content editor
  3393. metabox.insertAfter( '#wp-content-wrap' ).hide().addClass( 'attached-to-editor' );
  3394. // Switch to the Page Builder interface as soon as we load the page if there are widgets or the normal editor
  3395. // isn't supported.
  3396. var data = this.model.get( 'data' );
  3397. if ( ! _.isEmpty( data.widgets ) || ! _.isEmpty( data.grids ) || ! this.supports( 'revertToEditor' ) ) {
  3398. this.displayAttachedBuilder( { confirm: false } );
  3399. }
  3400. // We will also make this sticky if its attached to an editor.
  3401. var stickToolbar = function () {
  3402. var toolbar = thisView.$( '.so-builder-toolbar' );
  3403. if ( thisView.$el.hasClass( 'so-display-narrow' ) ) {
  3404. // In this case, we don't want to stick the toolbar.
  3405. toolbar.css( {
  3406. top: 0,
  3407. left: 0,
  3408. width: '100%',
  3409. position: 'absolute'
  3410. } );
  3411. thisView.$el.css( 'padding-top', toolbar.outerHeight() );
  3412. return;
  3413. }
  3414. var newTop = $( window ).scrollTop() - thisView.$el.offset().top;
  3415. if ( $( '#wpadminbar' ).css( 'position' ) === 'fixed' ) {
  3416. newTop += $( '#wpadminbar' ).outerHeight();
  3417. }
  3418. var limits = {
  3419. top: 0,
  3420. bottom: thisView.$el.outerHeight() - toolbar.outerHeight() + 20
  3421. };
  3422. if ( newTop > limits.top && newTop < limits.bottom ) {
  3423. if ( toolbar.css( 'position' ) !== 'fixed' ) {
  3424. // The toolbar needs to stick to the top, over the interface
  3425. toolbar.css( {
  3426. top: $( '#wpadminbar' ).outerHeight(),
  3427. left: thisView.$el.offset().left,
  3428. width: thisView.$el.outerWidth(),
  3429. position: 'fixed'
  3430. } );
  3431. }
  3432. } else {
  3433. // The toolbar needs to be at the top or bottom of the interface
  3434. toolbar.css( {
  3435. top: Math.min( Math.max( newTop, 0 ), thisView.$el.outerHeight() - toolbar.outerHeight() + 20 ),
  3436. left: 0,
  3437. width: '100%',
  3438. position: 'absolute'
  3439. } );
  3440. }
  3441. thisView.$el.css( 'padding-top', toolbar.outerHeight() );
  3442. };
  3443. this.on( 'builder_resize', stickToolbar, this );
  3444. $( document ).scroll( stickToolbar );
  3445. stickToolbar();
  3446. this.trigger('builder_attached_to_editor');
  3447. return this;
  3448. },
  3449. /**
  3450. * Display the builder interface when attached to a WordPress editor
  3451. */
  3452. displayAttachedBuilder: function( options ){
  3453. options = _.extend( {
  3454. confirm: true
  3455. }, options );
  3456. // Switch to the Page Builder interface
  3457. if( options.confirm ) {
  3458. var editor = typeof tinyMCE !== 'undefined' ? tinyMCE.get( 'content' ) : false;
  3459. var editorContent = ( editor && _.isFunction( editor.getContent ) ) ? editor.getContent() : $( 'textarea#content' ).val();
  3460. if ( editorContent !== '' && ! confirm( panelsOptions.loc.confirm_use_builder ) ) {
  3461. return false;
  3462. }
  3463. }
  3464. // Hide the standard content editor
  3465. $( '#wp-content-wrap' ).hide();
  3466. $( '#editor-expand-toggle' ).on( 'change.editor-expand', function () {
  3467. if ( ! $( this ).prop( 'checked' ) ) {
  3468. $( '#wp-content-wrap' ).hide();
  3469. }
  3470. } );
  3471. // Show page builder and the inside div
  3472. this.metabox.show().find( '> .inside' ).show();
  3473. // Triggers full refresh
  3474. $( window ).resize();
  3475. $( document ).scroll();
  3476. // Make sure the word count is visible
  3477. this.attachedVisible = true;
  3478. this.trigger( 'display_builder' );
  3479. return true;
  3480. },
  3481. /**
  3482. * Initialize the row sortables
  3483. */
  3484. initSortable: function () {
  3485. if( ! this.supports( 'moveRow' ) ) {
  3486. return this;
  3487. }
  3488. // Create the sortable for the rows
  3489. var builderView = this;
  3490. this.rowsSortable = this.$( '.so-rows-container' ).sortable( {
  3491. appendTo: '#wpwrap',
  3492. items: '.so-row-container',
  3493. handle: '.so-row-move',
  3494. axis: 'y',
  3495. tolerance: 'pointer',
  3496. scroll: false,
  3497. stop: function ( e, ui ) {
  3498. builderView.addHistoryEntry( 'row_moved' );
  3499. var $$ = $( ui.item ),
  3500. row = $$.data( 'view' );
  3501. builderView.model.get('rows').remove( row.model, {
  3502. 'silent' : true
  3503. } );
  3504. builderView.model.get('rows').add( row.model, {
  3505. 'silent' : true,
  3506. 'at' : $$.index()
  3507. } );
  3508. row.trigger( 'move', $$.index() );
  3509. builderView.model.refreshPanelsData();
  3510. }
  3511. } );
  3512. return this;
  3513. },
  3514. /**
  3515. * Refresh the row sortable
  3516. */
  3517. refreshSortable: function () {
  3518. // Refresh the sortable to account for the new row
  3519. if ( ! _.isNull( this.rowsSortable ) ) {
  3520. this.rowsSortable.sortable( 'refresh' );
  3521. }
  3522. },
  3523. /**
  3524. * Set the field that's used to store the data
  3525. * @param field
  3526. */
  3527. setDataField: function ( field, options ) {
  3528. options = _.extend( {
  3529. load: true
  3530. }, options );
  3531. this.dataField = field;
  3532. this.dataField.data( 'builder', this );
  3533. if ( options.load && field.val() !== '' ) {
  3534. var data = this.dataField.val();
  3535. try {
  3536. data = JSON.parse( data );
  3537. }
  3538. catch ( err ) {
  3539. data = {};
  3540. }
  3541. this.model.loadPanelsData( data );
  3542. this.currentData = data;
  3543. this.toggleWelcomeDisplay();
  3544. }
  3545. return this;
  3546. },
  3547. /**
  3548. * Store the model data in the data html field set in this.setDataField.
  3549. */
  3550. storeModelData: function () {
  3551. var data = JSON.stringify( this.model.get( 'data' ) );
  3552. if ( $( this.dataField ).val() !== data ) {
  3553. // If the data is different, set it and trigger a content_change event
  3554. $( this.dataField ).val( data );
  3555. $( this.dataField ).trigger( 'change' );
  3556. this.trigger( 'content_change' );
  3557. }
  3558. },
  3559. /**
  3560. * HAndle the visual side of adding a new row to the builder.
  3561. *
  3562. * @param row
  3563. * @param collection
  3564. * @param options
  3565. */
  3566. onAddRow: function ( row, collection, options ) {
  3567. options = _.extend( {noAnimate: false}, options );
  3568. // Create a view for the row
  3569. var rowView = new panels.view.row( {model: row} );
  3570. rowView.builder = this;
  3571. rowView.render();
  3572. // Attach the row elements to this builder
  3573. if ( _.isUndefined( options.at ) || collection.length <= 1 ) {
  3574. // Insert this at the end of the widgets container
  3575. rowView.$el.appendTo( this.$( '.so-rows-container' ) );
  3576. } else {
  3577. // We need to insert this at a specific position
  3578. rowView.$el.insertAfter(
  3579. this.$( '.so-rows-container .so-row-container' ).eq( options.at - 1 )
  3580. );
  3581. }
  3582. if ( options.noAnimate === false ) {
  3583. rowView.visualCreate();
  3584. }
  3585. this.refreshSortable();
  3586. rowView.resize();
  3587. },
  3588. /**
  3589. * Display the dialog to add a new widget.
  3590. *
  3591. * @returns {boolean}
  3592. */
  3593. displayAddWidgetDialog: function () {
  3594. this.dialogs.widgets.openDialog();
  3595. },
  3596. /**
  3597. * Display the dialog to add a new row.
  3598. */
  3599. displayAddRowDialog: function () {
  3600. var row = new panels.model.row();
  3601. var cells = new panels.collection.cells([{weight: 0.5}, {weight: 0.5}]);
  3602. cells.each(function (cell) {
  3603. cell.row = row;
  3604. });
  3605. row.set('cells', cells);
  3606. row.builder = this.model;
  3607. this.dialogs.row.setRowModel(row);
  3608. this.dialogs.row.openDialog();
  3609. },
  3610. /**
  3611. * Display the dialog to add prebuilt layouts.
  3612. *
  3613. * @returns {boolean}
  3614. */
  3615. displayAddPrebuiltDialog: function () {
  3616. this.dialogs.prebuilt.openDialog();
  3617. },
  3618. /**
  3619. * Display the history dialog.
  3620. *
  3621. * @returns {boolean}
  3622. */
  3623. displayHistoryDialog: function () {
  3624. this.dialogs.history.openDialog();
  3625. },
  3626. /**
  3627. * Handle pasting a row into the builder.
  3628. */
  3629. pasteRowHandler: function(){
  3630. var pastedModel = panels.helpers.clipboard.getModel( 'row-model' );
  3631. if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.row ) {
  3632. this.addHistoryEntry( 'row_pasted' );
  3633. pastedModel.builder = this.model;
  3634. this.model.get('rows').add( pastedModel, {
  3635. at: this.model.get('rows').indexOf( this.model ) + 1
  3636. } );
  3637. this.model.refreshPanelsData();
  3638. }
  3639. },
  3640. /**
  3641. * Get the model for the currently selected cell
  3642. */
  3643. getActiveCell: function ( options ) {
  3644. options = _.extend( {
  3645. createCell: true,
  3646. }, options );
  3647. if( ! this.model.get('rows').length ) {
  3648. // There aren't any rows yet
  3649. if ( options.createCell ) {
  3650. // Create a row with a single cell
  3651. this.model.addRow( {}, [{ weight: 1 }], { noAnimate: true } );
  3652. } else {
  3653. return null;
  3654. }
  3655. }
  3656. // Make sure the active cell isn't empty, and it's in a row that exists
  3657. var activeCell = this.activeCell;
  3658. if( _.isEmpty( activeCell ) || this.model.get('rows').indexOf( activeCell.model.row ) === -1 ) {
  3659. return this.model.get('rows').last().get('cells').first();
  3660. } else {
  3661. return activeCell.model;
  3662. }
  3663. },
  3664. /**
  3665. * Add a live editor to the builder
  3666. *
  3667. * @returns {panels.view.builder}
  3668. */
  3669. addLiveEditor: function ( ) {
  3670. if( _.isEmpty( this.config.liveEditorPreview ) ) {
  3671. return this;
  3672. }
  3673. // Create the live editor and set the builder to this.
  3674. this.liveEditor = new panels.view.liveEditor( {
  3675. builder: this,
  3676. previewUrl: this.config.liveEditorPreview
  3677. } );
  3678. // Display the live editor button in the toolbar
  3679. if ( this.liveEditor.hasPreviewUrl() ) {
  3680. this.$( '.so-builder-toolbar .so-live-editor' ).show();
  3681. }
  3682. this.trigger('builder_live_editor_added');
  3683. return this;
  3684. },
  3685. /**
  3686. * Show the current live editor
  3687. */
  3688. displayLiveEditor: function () {
  3689. if ( _.isUndefined( this.liveEditor ) ) {
  3690. return;
  3691. }
  3692. this.liveEditor.open();
  3693. },
  3694. /**
  3695. * Add the history browser.
  3696. *
  3697. * @return {panels.view.builder}
  3698. */
  3699. addHistoryBrowser: function () {
  3700. if( _.isEmpty( this.config.liveEditorPreview ) ) {
  3701. return this;
  3702. }
  3703. this.dialogs.history = new panels.dialog.history();
  3704. this.dialogs.history.builder = this;
  3705. this.dialogs.history.entries.builder = this.model;
  3706. // Set the revert entry
  3707. this.dialogs.history.setRevertEntry( this.model );
  3708. // Display the live editor button in the toolbar
  3709. this.$( '.so-builder-toolbar .so-history' ).show();
  3710. },
  3711. /**
  3712. * Add an entry.
  3713. *
  3714. * @param text
  3715. * @param data
  3716. */
  3717. addHistoryEntry: function ( text, data ) {
  3718. if ( _.isUndefined( data ) ) {
  3719. data = null;
  3720. }
  3721. if ( ! _.isUndefined( this.dialogs.history ) ) {
  3722. this.dialogs.history.entries.addEntry( text, data );
  3723. }
  3724. },
  3725. supports: function( thing ){
  3726. if( thing === 'rowAction' ) {
  3727. // Check if this supports any row action
  3728. return this.supports( 'addRow' ) || this.supports( 'editRow' ) || this.supports( 'deleteRow' );
  3729. } else if ( thing === 'widgetAction' ) {
  3730. // Check if this supports any widget action
  3731. return this.supports( 'addWidget' ) || this.supports( 'editWidget' ) || this.supports( 'deleteWidget' );
  3732. }
  3733. return _.isUndefined( this.config.builderSupports[ thing ] ) ? false : this.config.builderSupports[ thing ];
  3734. },
  3735. /**
  3736. * Handle a change of the content
  3737. */
  3738. handleContentChange: function () {
  3739. // Make sure we actually need to copy content.
  3740. if ( panelsOptions.copy_content && this.attachedToEditor && this.$el.is( ':visible' ) ) {
  3741. var panelsData = this.model.getPanelsData();
  3742. if( ! _.isEmpty( panelsData.widgets ) ) {
  3743. // We're going to create a copy of page builder content into the post content
  3744. $.post(
  3745. panelsOptions.ajaxurl,
  3746. {
  3747. action: 'so_panels_builder_content',
  3748. panels_data: JSON.stringify( panelsData ),
  3749. post_id: this.config.postId
  3750. },
  3751. function ( content ) {
  3752. if( content !== '' ) {
  3753. this.updateEditorContent( content );
  3754. }
  3755. }.bind( this )
  3756. );
  3757. }
  3758. }
  3759. },
  3760. /**
  3761. * Update editor content with the given content.
  3762. *
  3763. * @param content
  3764. */
  3765. updateEditorContent: function ( content ) {
  3766. // Switch back to the standard editor
  3767. if ( this.config.editorType !== 'tinyMCE' || typeof tinyMCE === 'undefined' || _.isNull( tinyMCE.get( "content" ) ) ) {
  3768. var $editor = $( this.config.editorId );
  3769. $editor.val( content ).trigger( 'change' ).trigger( 'keyup' );
  3770. } else {
  3771. var contentEd = tinyMCE.get( "content" );
  3772. contentEd.setContent( content );
  3773. contentEd.fire( 'change' );
  3774. contentEd.fire( 'keyup' );
  3775. }
  3776. this.triggerYoastSeoChange();
  3777. },
  3778. /**
  3779. * Trigger a change on Yoast SEO
  3780. */
  3781. triggerYoastSeoChange: function () {
  3782. if ( $( '#yoast_wpseo_focuskw_text_input' ).length ) {
  3783. var element = document.getElementById( 'yoast_wpseo_focuskw_text_input' ), event;
  3784. if ( document.createEvent ) {
  3785. event = document.createEvent( "HTMLEvents" );
  3786. event.initEvent( "keyup", true, true );
  3787. } else {
  3788. event = document.createEventObject();
  3789. event.eventType = "keyup";
  3790. }
  3791. event.eventName = "keyup";
  3792. if ( document.createEvent ) {
  3793. element.dispatchEvent( event );
  3794. } else {
  3795. element.fireEvent( "on" + event.eventType, event );
  3796. }
  3797. }
  3798. },
  3799. /**
  3800. * Handle displaying the builder
  3801. */
  3802. handleDisplayBuilder: function () {
  3803. var editor = typeof tinyMCE !== 'undefined' ? tinyMCE.get( 'content' ) : false;
  3804. var editorContent = ( editor && _.isFunction( editor.getContent ) ) ? editor.getContent() : $( 'textarea#content' ).val();
  3805. if (
  3806. (
  3807. _.isEmpty( this.model.get( 'data' ) ) ||
  3808. ( _.isEmpty( this.model.get( 'data' ).widgets ) && _.isEmpty( this.model.get( 'data' ).grids ) )
  3809. ) &&
  3810. editorContent !== ''
  3811. ) {
  3812. var editorClass = panelsOptions.text_widget;
  3813. // There is a small chance a theme will have removed this, so check
  3814. if ( _.isEmpty( editorClass ) ) {
  3815. return;
  3816. }
  3817. // Create the existing page content in a single widget
  3818. this.model.loadPanelsData( this.model.getPanelsDataFromHtml( editorContent, editorClass ) );
  3819. this.model.trigger( 'change' );
  3820. this.model.trigger( 'change:data' );
  3821. }
  3822. $('#post-status-info').addClass( 'for-siteorigin-panels' );
  3823. },
  3824. handleHideBuilder: function(){
  3825. $('#post-status-info').show().removeClass( 'for-siteorigin-panels' );
  3826. },
  3827. wrapEditorExpandAdjust: function( ){
  3828. try {
  3829. var events = ( $.hasData( window ) && $._data( window ) ).events.scroll,
  3830. event;
  3831. for( var i = 0; i < events.length; i++ ) {
  3832. if( events[i].namespace === 'editor-expand' ) {
  3833. event = events[i];
  3834. // Wrap the call
  3835. $( window ).unbind( 'scroll', event.handler );
  3836. $( window ).bind( 'scroll', function( e ){
  3837. if( ! this.attachedVisible ) {
  3838. event.handler( e );
  3839. }
  3840. }.bind( this ) );
  3841. break;
  3842. }
  3843. }
  3844. }
  3845. catch( e ){
  3846. // We tried, we failed
  3847. return;
  3848. }
  3849. },
  3850. /**
  3851. * Either add or remove the narrow class
  3852. * @returns {exports}
  3853. */
  3854. handleBuilderSizing: function () {
  3855. var width = this.$el.width();
  3856. if ( ! width ) {
  3857. return this;
  3858. }
  3859. if ( width < 480 ) {
  3860. this.$el.addClass( 'so-display-narrow' );
  3861. } else {
  3862. this.$el.removeClass( 'so-display-narrow' );
  3863. }
  3864. return this;
  3865. },
  3866. /**
  3867. * Set the parent dialog for all the dialogs in this builder.
  3868. *
  3869. * @param text
  3870. * @param dialog
  3871. */
  3872. setDialogParents: function ( text, dialog ) {
  3873. _.each( this.dialogs, function ( p, i, d ) {
  3874. d[i].setParent( text, dialog );
  3875. } );
  3876. // For any future dialogs
  3877. this.on( 'add_dialog', function ( newDialog ) {
  3878. newDialog.setParent( text, dialog );
  3879. }, this );
  3880. },
  3881. /**
  3882. * This shows or hides the welcome display depending on whether there are any rows in the collection.
  3883. */
  3884. toggleWelcomeDisplay: function () {
  3885. if ( ! this.model.get('rows').isEmpty() ) {
  3886. this.$( '.so-panels-welcome-message' ).hide();
  3887. } else {
  3888. this.$( '.so-panels-welcome-message' ).show();
  3889. }
  3890. },
  3891. /**
  3892. * Activate the contextual menu
  3893. * @param e
  3894. * @param menu
  3895. */
  3896. activateContextMenu: function ( e, menu ) {
  3897. var builder = this;
  3898. // Of all the visible builders, find the topmost
  3899. var topmostBuilder = $( '.siteorigin-panels-builder:visible' )
  3900. .sort( function ( a, b ) {
  3901. return $( a ).zIndex() > $( b ).zIndex() ? 1 : - 1;
  3902. } )
  3903. .last();
  3904. var topmostDialog = $( '.so-panels-dialog-wrapper:visible' )
  3905. .sort( function ( a, b ) {
  3906. return $( a ).zIndex() > $( b ).zIndex() ? 1 : - 1;
  3907. } )
  3908. .last();
  3909. var closestDialog = builder.$el.closest('.so-panels-dialog-wrapper');
  3910. // Only run this if its element is the topmost builder, in the topmost dialog
  3911. if (
  3912. builder.$el.is( topmostBuilder ) &&
  3913. (
  3914. topmostDialog.length === 0 ||
  3915. topmostDialog.is( closestDialog )
  3916. )
  3917. ) {
  3918. // Get the element we're currently hovering over
  3919. var over = $( [] )
  3920. .add( builder.$( '.so-panels-welcome-message:visible' ) )
  3921. .add( builder.$( '.so-rows-container > .so-row-container' ) )
  3922. .add( builder.$( '.so-cells > .cell' ) )
  3923. .add( builder.$( '.cell-wrapper > .so-widget' ) )
  3924. .filter( function ( i ) {
  3925. return menu.isOverEl( $( this ), e );
  3926. } );
  3927. var activeView = over.last().data( 'view' );
  3928. if ( activeView !== undefined && activeView.buildContextualMenu !== undefined ) {
  3929. // We'll pass this to the current active view so it can popular the contextual menu
  3930. activeView.buildContextualMenu( e, menu );
  3931. }
  3932. else if( over.last().hasClass( 'so-panels-welcome-message' ) ) {
  3933. // The user opened the contextual menu on the welcome message
  3934. this.buildContextualMenu( e, menu );
  3935. }
  3936. }
  3937. },
  3938. /**
  3939. * Build the contextual menu for the main builder - before any content has been added.
  3940. */
  3941. buildContextualMenu: function( e, menu ){
  3942. var actions = {};
  3943. if( this.supports( 'addRow' ) ) {
  3944. actions.add_row = { title: panelsOptions.loc.contextual.add_row };
  3945. }
  3946. if ( panels.helpers.clipboard.canCopyPaste() ) {
  3947. if( panels.helpers.clipboard.isModel( 'row-model' ) && this.supports( 'addRow' ) ) {
  3948. actions.paste_row = { title: panelsOptions.loc.contextual.row_paste };
  3949. }
  3950. }
  3951. if( ! _.isEmpty( actions ) ) {
  3952. menu.addSection(
  3953. 'builder-actions',
  3954. {
  3955. sectionTitle: panelsOptions.loc.contextual.row_actions,
  3956. search: false,
  3957. },
  3958. actions,
  3959. function ( c ) {
  3960. switch ( c ) {
  3961. case 'add_row':
  3962. this.displayAddRowDialog();
  3963. break;
  3964. case 'paste_row':
  3965. this.pasteRowHandler();
  3966. break;
  3967. }
  3968. }.bind( this )
  3969. );
  3970. }
  3971. },
  3972. } );
  3973. },{}],24:[function(require,module,exports){
  3974. var panels = window.panels, $ = jQuery;
  3975. module.exports = Backbone.View.extend( {
  3976. template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-cell' ).html() ) ),
  3977. events: {
  3978. 'click .cell-wrapper': 'handleCellClick'
  3979. },
  3980. /* The row view that this cell is a part of */
  3981. row: null,
  3982. widgetSortable: null,
  3983. initialize: function () {
  3984. this.model.get('widgets').on( 'add', this.onAddWidget, this );
  3985. },
  3986. /**
  3987. * Render the actual cell
  3988. */
  3989. render: function () {
  3990. var templateArgs = {
  3991. weight: this.model.get( 'weight' ),
  3992. totalWeight: this.row.model.get('cells').totalWeight()
  3993. };
  3994. this.setElement( this.template( templateArgs ) );
  3995. this.$el.data( 'view', this );
  3996. // Now lets render any widgets that are currently in the row
  3997. var thisView = this;
  3998. this.model.get('widgets').each( function ( widget ) {
  3999. var widgetView = new panels.view.widget( {model: widget} );
  4000. widgetView.cell = thisView;
  4001. widgetView.render();
  4002. widgetView.$el.appendTo( thisView.$( '.widgets-container' ) );
  4003. } );
  4004. this.initSortable();
  4005. this.initResizable();
  4006. return this;
  4007. },
  4008. /**
  4009. * Initialize the widget sortable
  4010. */
  4011. initSortable: function () {
  4012. if( ! this.row.builder.supports( 'moveWidget' ) ) {
  4013. return this;
  4014. }
  4015. var cellView = this;
  4016. // Go up the view hierarchy until we find the ID attribute
  4017. var builderID = cellView.row.builder.$el.attr( 'id' );
  4018. // Create a widget sortable that's connected with all other cells
  4019. this.widgetSortable = this.$( '.widgets-container' ).sortable( {
  4020. placeholder: "so-widget-sortable-highlight",
  4021. connectWith: '#' + builderID + ' .so-cells .cell .widgets-container',
  4022. tolerance: 'pointer',
  4023. scroll: false,
  4024. over: function ( e, ui ) {
  4025. // This will make all the rows in the current builder resize
  4026. cellView.row.builder.trigger( 'widget_sortable_move' );
  4027. },
  4028. stop: function ( e, ui ) {
  4029. cellView.row.builder.addHistoryEntry( 'widget_moved' );
  4030. var $$ = $( ui.item ),
  4031. widget = $$.data( 'view' ),
  4032. targetCell = $$.closest( '.cell' ).data( 'view' );
  4033. // Move the model and the view to the new cell
  4034. widget.model.moveToCell( targetCell.model, {}, $$.index() );
  4035. widget.cell = targetCell;
  4036. widget.cell.row.builder.model.refreshPanelsData();
  4037. },
  4038. helper: function ( e, el ) {
  4039. var helper = el.clone()
  4040. .css( {
  4041. 'width': el.outerWidth(),
  4042. 'z-index': 10000,
  4043. 'position': 'fixed'
  4044. } )
  4045. .addClass( 'widget-being-dragged' ).appendTo( 'body' );
  4046. // Center the helper to the mouse cursor.
  4047. if ( el.outerWidth() > 720 ) {
  4048. helper.animate( {
  4049. 'margin-left': e.pageX - el.offset().left - (
  4050. 480 / 2
  4051. ),
  4052. 'width': 480
  4053. }, 'fast' );
  4054. }
  4055. return helper;
  4056. }
  4057. } );
  4058. return this;
  4059. },
  4060. /**
  4061. * Refresh the widget sortable when a new widget is added
  4062. */
  4063. refreshSortable: function () {
  4064. if ( ! _.isNull( this.widgetSortable ) ) {
  4065. this.widgetSortable.sortable( 'refresh' );
  4066. }
  4067. },
  4068. /**
  4069. * This will make the cell resizble
  4070. */
  4071. initResizable: function () {
  4072. if( ! this.row.builder.supports( 'editRow' ) ) {
  4073. return this;
  4074. }
  4075. // var neighbor = this.$el.previous().data('view');
  4076. var handle = this.$( '.resize-handle' ).css( 'position', 'absolute' );
  4077. var container = this.row.$el;
  4078. var cellView = this;
  4079. // The view of the cell to the left is stored when dragging starts.
  4080. var previousCell;
  4081. handle.draggable( {
  4082. axis: 'x',
  4083. containment: container,
  4084. start: function ( e, ui ) {
  4085. // Set the containment to the cell parent
  4086. previousCell = cellView.$el.prev().data( 'view' );
  4087. if ( _.isUndefined( previousCell ) ) {
  4088. return;
  4089. }
  4090. // Create the clone for the current cell
  4091. var newCellClone = cellView.$el.clone().appendTo( ui.helper ).css( {
  4092. position: 'absolute',
  4093. top: '0',
  4094. width: cellView.$el.outerWidth(),
  4095. left: 5,
  4096. height: cellView.$el.outerHeight()
  4097. } );
  4098. newCellClone.find( '.resize-handle' ).remove();
  4099. // Create the clone for the previous cell
  4100. var prevCellClone = previousCell.$el.clone().appendTo( ui.helper ).css( {
  4101. position: 'absolute',
  4102. top: '0',
  4103. width: previousCell.$el.outerWidth(),
  4104. right: 5,
  4105. height: previousCell.$el.outerHeight()
  4106. } );
  4107. prevCellClone.find( '.resize-handle' ).remove();
  4108. $( this ).data( {
  4109. 'newCellClone': newCellClone,
  4110. 'prevCellClone': prevCellClone
  4111. } );
  4112. },
  4113. drag: function ( e, ui ) {
  4114. // Calculate the new cell and previous cell widths as a percent
  4115. var containerWidth = cellView.row.$el.width() + 10;
  4116. var ncw = cellView.model.get( 'weight' ) - (
  4117. (
  4118. ui.position.left + handle.outerWidth() / 2
  4119. ) / containerWidth
  4120. );
  4121. var pcw = previousCell.model.get( 'weight' ) + (
  4122. (
  4123. ui.position.left + handle.outerWidth() / 2
  4124. ) / containerWidth
  4125. );
  4126. $( this ).data( 'newCellClone' ).css( 'width', containerWidth * ncw )
  4127. .find( '.preview-cell-weight' ).html( Math.round( ncw * 1000 ) / 10 );
  4128. $( this ).data( 'prevCellClone' ).css( 'width', containerWidth * pcw )
  4129. .find( '.preview-cell-weight' ).html( Math.round( pcw * 1000 ) / 10 );
  4130. },
  4131. stop: function ( e, ui ) {
  4132. // Remove the clones
  4133. $( this ).data( 'newCellClone' ).remove();
  4134. $( this ).data( 'prevCellClone' ).remove();
  4135. var containerWidth = cellView.row.$el.width() + 10;
  4136. var ncw = cellView.model.get( 'weight' ) - (
  4137. (
  4138. ui.position.left + handle.outerWidth() / 2
  4139. ) / containerWidth
  4140. );
  4141. var pcw = previousCell.model.get( 'weight' ) + (
  4142. (
  4143. ui.position.left + handle.outerWidth() / 2
  4144. ) / containerWidth
  4145. );
  4146. if ( ncw > 0.02 && pcw > 0.02 ) {
  4147. cellView.row.builder.addHistoryEntry( 'cell_resized' );
  4148. cellView.model.set( 'weight', ncw );
  4149. previousCell.model.set( 'weight', pcw );
  4150. cellView.row.resize();
  4151. }
  4152. ui.helper.css( 'left', - handle.outerWidth() / 2 );
  4153. // Refresh the panels data
  4154. cellView.row.builder.model.refreshPanelsData();
  4155. }
  4156. } );
  4157. return this;
  4158. },
  4159. /**
  4160. * This is triggered when ever a widget is added to the row collection.
  4161. *
  4162. * @param widget
  4163. */
  4164. onAddWidget: function ( widget, collection, options ) {
  4165. options = _.extend( {noAnimate: false}, options );
  4166. // Create the view for the widget
  4167. var view = new panels.view.widget( {
  4168. model: widget
  4169. } );
  4170. view.cell = this;
  4171. if ( _.isUndefined( widget.isDuplicate ) ) {
  4172. widget.isDuplicate = false;
  4173. }
  4174. // Render and load the form if this is a duplicate
  4175. view.render( {
  4176. 'loadForm': widget.isDuplicate
  4177. } );
  4178. if ( _.isUndefined( options.at ) || collection.length <= 1 ) {
  4179. // Insert this at the end of the widgets container
  4180. view.$el.appendTo( this.$( '.widgets-container' ) );
  4181. } else {
  4182. // We need to insert this at a specific position
  4183. view.$el.insertAfter(
  4184. this.$( '.widgets-container .so-widget' ).eq( options.at - 1 )
  4185. );
  4186. }
  4187. if ( options.noAnimate === false ) {
  4188. // We need an animation
  4189. view.visualCreate();
  4190. }
  4191. this.refreshSortable();
  4192. this.row.resize();
  4193. },
  4194. /**
  4195. * Handle this cell being clicked on
  4196. *
  4197. * @param e
  4198. * @returns {boolean}
  4199. */
  4200. handleCellClick: function ( e ) {
  4201. // Remove all existing selected cell indication for this builder
  4202. this.row.builder.$el.find( '.so-cells .cell' ).removeClass( 'cell-selected' );
  4203. if( this.row.builder.activeCell === this && ! this.model.get('widgets').length ) {
  4204. // This is a click on an empty cell
  4205. this.row.builder.activeCell = null;
  4206. }
  4207. else {
  4208. this.$el.addClass( 'cell-selected' );
  4209. this.row.builder.activeCell = this;
  4210. }
  4211. },
  4212. /**
  4213. * Insert a widget from the clipboard
  4214. */
  4215. pasteHandler: function(){
  4216. var pastedModel = panels.helpers.clipboard.getModel( 'widget-model' );
  4217. if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.widget ) {
  4218. this.row.builder.addHistoryEntry( 'widget_pasted' );
  4219. pastedModel.cell = this.model;
  4220. this.model.get('widgets').add( pastedModel );
  4221. this.row.builder.model.refreshPanelsData();
  4222. }
  4223. },
  4224. /**
  4225. * Build up the contextual menu for a cell
  4226. *
  4227. * @param e
  4228. * @param menu
  4229. */
  4230. buildContextualMenu: function ( e, menu ) {
  4231. var thisView = this;
  4232. if( ! menu.hasSection( 'add-widget-below' ) ) {
  4233. menu.addSection(
  4234. 'add-widget-cell',
  4235. {
  4236. sectionTitle: panelsOptions.loc.contextual.add_widget_cell,
  4237. searchPlaceholder: panelsOptions.loc.contextual.search_widgets,
  4238. defaultDisplay: panelsOptions.contextual.default_widgets
  4239. },
  4240. panelsOptions.widgets,
  4241. function ( c ) {
  4242. thisView.row.builder.addHistoryEntry( 'widget_added' );
  4243. var widget = new panels.model.widget( {
  4244. class: c
  4245. } );
  4246. // Add the widget to the cell model
  4247. widget.cell = thisView.model;
  4248. widget.cell.get('widgets').add( widget );
  4249. thisView.row.builder.model.refreshPanelsData();
  4250. }
  4251. );
  4252. }
  4253. var actions = {};
  4254. if ( this.row.builder.supports('addWidget') && panels.helpers.clipboard.isModel( 'widget-model' ) ) {
  4255. actions.paste = {title: panelsOptions.loc.contextual.cell_paste_widget};
  4256. }
  4257. if( ! _.isEmpty( actions ) ) {
  4258. menu.addSection(
  4259. 'cell-actions',
  4260. {
  4261. sectionTitle: panelsOptions.loc.contextual.cell_actions,
  4262. search: false,
  4263. },
  4264. actions,
  4265. function ( c ) {
  4266. switch ( c ) {
  4267. case 'paste':
  4268. this.pasteHandler();
  4269. break;
  4270. }
  4271. this.row.builder.model.refreshPanelsData();
  4272. }.bind( this )
  4273. );
  4274. }
  4275. // Add the contextual menu for the parent row
  4276. this.row.buildContextualMenu( e, menu );
  4277. }
  4278. } );
  4279. },{}],25:[function(require,module,exports){
  4280. var panels = window.panels, $ = jQuery;
  4281. module.exports = Backbone.View.extend( {
  4282. dialogTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog' ).html() ) ),
  4283. dialogTabTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-tab' ).html() ) ),
  4284. tabbed: false,
  4285. rendered: false,
  4286. builder: false,
  4287. className: 'so-panels-dialog-wrapper',
  4288. dialogClass: '',
  4289. dialogIcon: '',
  4290. parentDialog: false,
  4291. dialogOpen: false,
  4292. editableLabel: false,
  4293. events: {
  4294. 'click .so-close': 'closeDialog',
  4295. 'click .so-nav.so-previous': 'navToPrevious',
  4296. 'click .so-nav.so-next': 'navToNext',
  4297. },
  4298. initialize: function () {
  4299. // The first time this dialog is opened, render it
  4300. this.once( 'open_dialog', this.render );
  4301. this.once( 'open_dialog', this.attach );
  4302. this.once( 'open_dialog', this.setDialogClass );
  4303. this.trigger( 'initialize_dialog', this );
  4304. if ( ! _.isUndefined( this.initializeDialog ) ) {
  4305. this.initializeDialog();
  4306. }
  4307. },
  4308. /**
  4309. * Returns the next dialog in the sequence. Should be overwritten by a child dialog.
  4310. * @returns {null}
  4311. */
  4312. getNextDialog: function () {
  4313. return null;
  4314. },
  4315. /**
  4316. * Returns the previous dialog in this sequence. Should be overwritten by child dialog.
  4317. * @returns {null}
  4318. */
  4319. getPrevDialog: function () {
  4320. return null;
  4321. },
  4322. /**
  4323. * Adds a dialog class to uniquely identify this dialog type
  4324. */
  4325. setDialogClass: function () {
  4326. if ( this.dialogClass !== '' ) {
  4327. this.$( '.so-panels-dialog' ).addClass( this.dialogClass );
  4328. }
  4329. },
  4330. /**
  4331. * Set the builder that controls this dialog.
  4332. * @param {panels.view.builder} builder
  4333. */
  4334. setBuilder: function ( builder ) {
  4335. this.builder = builder;
  4336. // Trigger an add dialog event on the builder so it can modify the dialog in any way
  4337. builder.trigger( 'add_dialog', this, this.builder );
  4338. return this;
  4339. },
  4340. /**
  4341. * Attach the dialog to the window
  4342. */
  4343. attach: function () {
  4344. this.$el.appendTo( 'body' );
  4345. return this;
  4346. },
  4347. /**
  4348. * Converts an HTML representation of the dialog into arguments for a dialog box
  4349. * @param html HTML for the dialog
  4350. * @param args Arguments passed to the template
  4351. * @returns {}
  4352. */
  4353. parseDialogContent: function ( html, args ) {
  4354. // Add a CID
  4355. args = _.extend( {cid: this.cid}, args );
  4356. var c = $( (
  4357. _.template( panels.helpers.utils.processTemplate( html ) )
  4358. )( args ) );
  4359. var r = {
  4360. title: c.find( '.title' ).html(),
  4361. buttons: c.find( '.buttons' ).html(),
  4362. content: c.find( '.content' ).html()
  4363. };
  4364. if ( c.has( '.left-sidebar' ) ) {
  4365. r.left_sidebar = c.find( '.left-sidebar' ).html();
  4366. }
  4367. if ( c.has( '.right-sidebar' ) ) {
  4368. r.right_sidebar = c.find( '.right-sidebar' ).html();
  4369. }
  4370. return r;
  4371. },
  4372. /**
  4373. * Render the dialog and initialize the tabs
  4374. *
  4375. * @param attributes
  4376. * @returns {panels.view.dialog}
  4377. */
  4378. renderDialog: function ( attributes ) {
  4379. attributes = _.extend( {
  4380. editableLabel: this.editableLabel,
  4381. dialogIcon: this.dialogIcon,
  4382. }, attributes );
  4383. this.$el.html( this.dialogTemplate( attributes ) ).hide();
  4384. this.$el.data( 'view', this );
  4385. this.$el.addClass( 'so-panels-dialog-wrapper' );
  4386. if ( this.parentDialog !== false ) {
  4387. // Add a link to the parent dialog as a sort of crumbtrail.
  4388. var thisDialog = this;
  4389. var dialogParent = $( '<h3 class="so-parent-link"></h3>' ).html( this.parentDialog.text + '<div class="so-separator"></div>' );
  4390. dialogParent.click( function ( e ) {
  4391. e.preventDefault();
  4392. thisDialog.closeDialog();
  4393. thisDialog.parentDialog.openDialog();
  4394. } );
  4395. this.$( '.so-title-bar' ).prepend( dialogParent );
  4396. }
  4397. if( this.$( '.so-title-bar .so-title-editable' ).length ) {
  4398. // Added here because .so-edit-title is only available after the template has been rendered.
  4399. this.initEditableLabel();
  4400. }
  4401. return this;
  4402. },
  4403. /**
  4404. * Initialize the sidebar tabs
  4405. */
  4406. initTabs: function () {
  4407. var tabs = this.$( '.so-sidebar-tabs li a' );
  4408. if ( tabs.length === 0 ) {
  4409. return this;
  4410. }
  4411. var thisDialog = this;
  4412. tabs.click( function ( e ) {
  4413. e.preventDefault();
  4414. var $$ = $( this );
  4415. thisDialog.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
  4416. thisDialog.$( '.so-content .so-content-tabs > *' ).hide();
  4417. $$.parent().addClass( 'tab-active' );
  4418. var url = $$.attr( 'href' );
  4419. if ( ! _.isUndefined( url ) && url.charAt( 0 ) === '#' ) {
  4420. // Display the new tab
  4421. var tabName = url.split( '#' )[1];
  4422. thisDialog.$( '.so-content .so-content-tabs .tab-' + tabName ).show();
  4423. }
  4424. // This lets other dialogs implement their own custom handlers
  4425. thisDialog.trigger( 'tab_click', $$ );
  4426. } );
  4427. // Trigger a click on the first tab
  4428. this.$( '.so-sidebar-tabs li a' ).first().click();
  4429. return this;
  4430. },
  4431. initToolbar: function () {
  4432. // Trigger simplified click event for elements marked as toolbar buttons.
  4433. var buttons = this.$( '.so-toolbar .so-buttons .so-toolbar-button' );
  4434. buttons.click( function ( e ) {
  4435. e.preventDefault();
  4436. this.trigger( 'button_click', $( e.currentTarget ) );
  4437. }.bind( this ) );
  4438. // Handle showing and hiding the dropdown list items
  4439. var $dropdowns = this.$( '.so-toolbar .so-buttons .so-dropdown-button' );
  4440. $dropdowns.click( function ( e ) {
  4441. e.preventDefault();
  4442. var $dropdownButton = $( e.currentTarget );
  4443. var $dropdownList = $dropdownButton.siblings( '.so-dropdown-links-wrapper' );
  4444. if ( $dropdownList.is( '.hidden' ) ) {
  4445. $dropdownList.removeClass( 'hidden' );
  4446. } else {
  4447. $dropdownList.addClass( 'hidden' );
  4448. }
  4449. }.bind( this ) );
  4450. // Hide dropdown list on click anywhere, unless it's a dropdown option which requires confirmation in it's
  4451. // unconfirmed state.
  4452. $( 'html' ).click( function ( e ) {
  4453. this.$( '.so-dropdown-links-wrapper' ).not( '.hidden' ).each( function ( index, el ) {
  4454. var $dropdownList = $( el );
  4455. var $trgt = $( e.target );
  4456. if ( $trgt.length === 0 || !(
  4457. (
  4458. $trgt.is('.so-needs-confirm') && !$trgt.is('.so-confirmed')
  4459. ) || $trgt.is('.so-dropdown-button')
  4460. ) ) {
  4461. $dropdownList.addClass('hidden');
  4462. }
  4463. } );
  4464. }.bind( this ) );
  4465. },
  4466. /**
  4467. * Initialize the editable dialog title
  4468. */
  4469. initEditableLabel: function(){
  4470. var $editElt = this.$( '.so-title-bar .so-title-editable' );
  4471. $editElt.keypress( function ( event ) {
  4472. var enterPressed = event.type === 'keypress' && event.keyCode === 13;
  4473. if ( enterPressed ) {
  4474. // Need to make sure tab focus is on another element, otherwise pressing enter multiple times refocuses
  4475. // the element and allows newlines.
  4476. var tabbables = $( ':tabbable' );
  4477. var curTabIndex = tabbables.index( $editElt );
  4478. tabbables.eq( curTabIndex + 1 ).focus();
  4479. // After the above, we're somehow left with the first letter of text selected,
  4480. // so this removes the selection.
  4481. window.getSelection().removeAllRanges();
  4482. }
  4483. return !enterPressed;
  4484. } ).blur( function () {
  4485. var newValue = $editElt.text().replace( /^\s+|\s+$/gm, '' );
  4486. var oldValue = $editElt.data( 'original-value' ).replace( /^\s+|\s+$/gm, '' );
  4487. if ( newValue !== oldValue ) {
  4488. $editElt.text( newValue );
  4489. this.trigger( 'edit_label', newValue );
  4490. }
  4491. }.bind( this ) );
  4492. $editElt.focus( function() {
  4493. $editElt.data( 'original-value', $editElt.text() );
  4494. panels.helpers.utils.selectElementContents( this );
  4495. } );
  4496. },
  4497. /**
  4498. * Quickly setup the dialog by opening and closing it.
  4499. */
  4500. setupDialog: function () {
  4501. this.openDialog();
  4502. this.closeDialog();
  4503. },
  4504. /**
  4505. * Refresh the next and previous buttons.
  4506. */
  4507. refreshDialogNav: function () {
  4508. this.$( '.so-title-bar .so-nav' ).show().removeClass( 'so-disabled' );
  4509. // Lets also hide the next and previous if we don't have a next and previous dialog
  4510. var nextDialog = this.getNextDialog();
  4511. var nextButton = this.$( '.so-title-bar .so-next' );
  4512. var prevDialog = this.getPrevDialog();
  4513. var prevButton = this.$( '.so-title-bar .so-previous' );
  4514. if ( nextDialog === null ) {
  4515. nextButton.hide();
  4516. }
  4517. else if ( nextDialog === false ) {
  4518. nextButton.addClass( 'so-disabled' );
  4519. }
  4520. if ( prevDialog === null ) {
  4521. prevButton.hide();
  4522. }
  4523. else if ( prevDialog === false ) {
  4524. prevButton.addClass( 'so-disabled' );
  4525. }
  4526. },
  4527. /**
  4528. * Open the dialog
  4529. */
  4530. openDialog: function ( options ) {
  4531. options = _.extend( {
  4532. silent: false
  4533. }, options );
  4534. if ( ! options.silent ) {
  4535. this.trigger( 'open_dialog' );
  4536. }
  4537. this.dialogOpen = true;
  4538. this.refreshDialogNav();
  4539. // Stop scrolling for the main body
  4540. panels.helpers.pageScroll.lock();
  4541. // Start listen for keyboard keypresses.
  4542. $( window ).on( 'keyup', this.keyboardListen );
  4543. this.$el.show();
  4544. if ( ! options.silent ) {
  4545. // This triggers once everything is visible
  4546. this.trigger( 'open_dialog_complete' );
  4547. this.builder.trigger( 'open_dialog', this );
  4548. $( document ).trigger( 'open_dialog', this );
  4549. }
  4550. },
  4551. /**
  4552. * Close the dialog
  4553. *
  4554. * @param e
  4555. * @returns {boolean}
  4556. */
  4557. closeDialog: function ( options ) {
  4558. options = _.extend( {
  4559. silent: false
  4560. }, options );
  4561. if ( ! options.silent ) {
  4562. this.trigger( 'close_dialog' );
  4563. }
  4564. this.dialogOpen = false;
  4565. this.$el.hide();
  4566. panels.helpers.pageScroll.unlock();
  4567. // Stop listen for keyboard keypresses.
  4568. $( window ).off( 'keyup', this.keyboardListen );
  4569. if ( ! options.silent ) {
  4570. // This triggers once everything is hidden
  4571. this.trigger( 'close_dialog_complete' );
  4572. this.builder.trigger( 'close_dialog', this );
  4573. }
  4574. },
  4575. /**
  4576. * Keyboard events handler
  4577. */
  4578. keyboardListen: function ( e ) {
  4579. // [Esc] to close
  4580. if ( e.which === 27 ) {
  4581. $( '.so-panels-dialog-wrapper .so-close' ).trigger( 'click' );
  4582. }
  4583. },
  4584. /**
  4585. * Navigate to the previous dialog
  4586. */
  4587. navToPrevious: function () {
  4588. this.closeDialog();
  4589. var prev = this.getPrevDialog();
  4590. if ( prev !== null && prev !== false ) {
  4591. prev.openDialog();
  4592. }
  4593. },
  4594. /**
  4595. * Navigate to the next dialog
  4596. */
  4597. navToNext: function () {
  4598. this.closeDialog();
  4599. var next = this.getNextDialog();
  4600. if ( next !== null && next !== false ) {
  4601. next.openDialog();
  4602. }
  4603. },
  4604. /**
  4605. * Get the values from the form and convert them into a data array
  4606. */
  4607. getFormValues: function ( formSelector ) {
  4608. if ( _.isUndefined( formSelector ) ) {
  4609. formSelector = '.so-content';
  4610. }
  4611. var $f = this.$( formSelector );
  4612. var data = {}, parts;
  4613. // Find all the named fields in the form
  4614. $f.find( '[name]' ).each( function () {
  4615. var $$ = $( this );
  4616. try {
  4617. var name = /([A-Za-z_]+)\[(.*)\]/.exec( $$.attr( 'name' ) );
  4618. if ( _.isEmpty( name ) ) {
  4619. return true;
  4620. }
  4621. // Create an array with the parts of the name
  4622. if ( _.isUndefined( name[2] ) ) {
  4623. parts = $$.attr( 'name' );
  4624. } else {
  4625. parts = name[2].split( '][' );
  4626. parts.unshift( name[1] );
  4627. }
  4628. parts = parts.map( function ( e ) {
  4629. if ( ! isNaN( parseFloat( e ) ) && isFinite( e ) ) {
  4630. return parseInt( e );
  4631. } else {
  4632. return e;
  4633. }
  4634. } );
  4635. var sub = data;
  4636. var fieldValue = null;
  4637. var fieldType = (
  4638. _.isString( $$.attr( 'type' ) ) ? $$.attr( 'type' ).toLowerCase() : false
  4639. );
  4640. // First we need to get the value from the field
  4641. if ( fieldType === 'checkbox' ) {
  4642. if ( $$.is( ':checked' ) ) {
  4643. fieldValue = $$.val() !== '' ? $$.val() : true;
  4644. } else {
  4645. fieldValue = null;
  4646. }
  4647. }
  4648. else if ( fieldType === 'radio' ) {
  4649. if ( $$.is( ':checked' ) ) {
  4650. fieldValue = $$.val();
  4651. } else {
  4652. //skip over unchecked radios
  4653. return;
  4654. }
  4655. }
  4656. else if ( $$.prop( 'tagName' ) === 'TEXTAREA' && $$.hasClass( 'wp-editor-area' ) ) {
  4657. // This is a TinyMCE editor, so we'll use the tinyMCE object to get the content
  4658. var editor = null;
  4659. if ( typeof tinyMCE !== 'undefined' ) {
  4660. editor = tinyMCE.get( $$.attr( 'id' ) );
  4661. }
  4662. if ( editor !== null && _.isFunction( editor.getContent ) && ! editor.isHidden() ) {
  4663. fieldValue = editor.getContent();
  4664. } else {
  4665. fieldValue = $$.val();
  4666. }
  4667. }
  4668. else if ( $$.prop( 'tagName' ) === 'SELECT' ) {
  4669. var selected = $$.find( 'option:selected' );
  4670. if ( selected.length === 1 ) {
  4671. fieldValue = $$.find( 'option:selected' ).val();
  4672. }
  4673. else if ( selected.length > 1 ) {
  4674. // This is a mutli-select field
  4675. fieldValue = _.map( $$.find( 'option:selected' ), function ( n, i ) {
  4676. return $( n ).val();
  4677. } );
  4678. }
  4679. } else {
  4680. // This is a fallback that will work for most fields
  4681. fieldValue = $$.val();
  4682. }
  4683. // Now, we need to filter this value if necessary
  4684. if ( ! _.isUndefined( $$.data( 'panels-filter' ) ) ) {
  4685. switch ( $$.data( 'panels-filter' ) ) {
  4686. case 'json_parse':
  4687. // Attempt to parse the JSON value of this field
  4688. try {
  4689. fieldValue = JSON.parse( fieldValue );
  4690. }
  4691. catch ( err ) {
  4692. fieldValue = '';
  4693. }
  4694. break;
  4695. }
  4696. }
  4697. // Now convert this into an array
  4698. if ( fieldValue !== null ) {
  4699. for ( var i = 0; i < parts.length; i ++ ) {
  4700. if ( i === parts.length - 1 ) {
  4701. if ( parts[i] === '' ) {
  4702. // This needs to be an array
  4703. sub.push( fieldValue );
  4704. } else {
  4705. sub[parts[i]] = fieldValue;
  4706. }
  4707. } else {
  4708. if ( _.isUndefined( sub[parts[i]] ) ) {
  4709. if ( parts[i + 1] === '' ) {
  4710. sub[parts[i]] = [];
  4711. } else {
  4712. sub[parts[i]] = {};
  4713. }
  4714. }
  4715. sub = sub[parts[i]];
  4716. }
  4717. }
  4718. }
  4719. }
  4720. catch ( error ) {
  4721. // Ignore this error, just log the message for debugging
  4722. console.log( 'Field [' + $$.attr('name') + '] could not be processed and was skipped - ' + error.message );
  4723. }
  4724. } ); // End of each through input fields
  4725. return data;
  4726. },
  4727. /**
  4728. * Set a status message for the dialog
  4729. */
  4730. setStatusMessage: function ( message, loading ) {
  4731. this.$( '.so-toolbar .so-status' ).html( message );
  4732. if ( ! _.isUndefined( loading ) && loading ) {
  4733. this.$( '.so-toolbar .so-status' ).addClass( 'so-panels-loading' );
  4734. }
  4735. },
  4736. /**
  4737. * Set the parent after.
  4738. */
  4739. setParent: function ( text, dialog ) {
  4740. this.parentDialog = {
  4741. text: text,
  4742. dialog: dialog
  4743. };
  4744. }
  4745. } );
  4746. },{}],26:[function(require,module,exports){
  4747. var panels = window.panels, $ = jQuery;
  4748. module.exports = Backbone.View.extend( {
  4749. template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-live-editor' ).html() ) ),
  4750. previewScrollTop: 0,
  4751. loadTimes: [],
  4752. previewFrameId: 1,
  4753. previewUrl: null,
  4754. previewIframe: null,
  4755. events: {
  4756. 'click .live-editor-close': 'close',
  4757. 'click .live-editor-collapse': 'collapse',
  4758. 'click .live-editor-mode': 'mobileToggle'
  4759. },
  4760. initialize: function ( options ) {
  4761. options = _.extend( {
  4762. builder: false,
  4763. previewUrl: false,
  4764. }, options );
  4765. if( _.isEmpty( options.previewUrl ) ) {
  4766. options.previewUrl = panelsOptions.ajaxurl + "&action=so_panels_live_editor_preview";
  4767. }
  4768. this.builder = options.builder;
  4769. this.previewUrl = options.previewUrl;
  4770. this.builder.model.on( 'refresh_panels_data', this.handleRefreshData, this );
  4771. this.builder.model.on( 'load_panels_data', this.handleLoadData, this );
  4772. },
  4773. /**
  4774. * Render the live editor
  4775. */
  4776. render: function () {
  4777. this.setElement( this.template() );
  4778. this.$el.hide();
  4779. var thisView = this;
  4780. var isMouseDown = false;
  4781. $( document )
  4782. .mousedown( function () {
  4783. isMouseDown = true;
  4784. } )
  4785. .mouseup( function () {
  4786. isMouseDown = false;
  4787. } );
  4788. // Handle highlighting the relevant widget in the live editor preview
  4789. this.$el.on( 'mouseenter', '.so-widget-wrapper', function () {
  4790. var $$ = $( this ),
  4791. previewWidget = $$.data( 'live-editor-preview-widget' );
  4792. if ( ! isMouseDown && previewWidget !== undefined && previewWidget.length && ! thisView.$( '.so-preview-overlay' ).is( ':visible' ) ) {
  4793. thisView.highlightElement( previewWidget );
  4794. thisView.scrollToElement( previewWidget );
  4795. }
  4796. } );
  4797. thisView.$el.on( 'mouseleave', '.so-widget-wrapper', function () {
  4798. thisView.resetHighlights();
  4799. } );
  4800. thisView.builder.on( 'open_dialog', function () {
  4801. thisView.resetHighlights();
  4802. } );
  4803. return this;
  4804. },
  4805. /**
  4806. * Attach the live editor to the document
  4807. */
  4808. attach: function () {
  4809. this.$el.appendTo( 'body' );
  4810. },
  4811. /**
  4812. * Display the live editor
  4813. */
  4814. open: function () {
  4815. if ( this.$el.html() === '' ) {
  4816. this.render();
  4817. }
  4818. if ( this.$el.closest( 'body' ).length === 0 ) {
  4819. this.attach();
  4820. }
  4821. // Disable page scrolling
  4822. panels.helpers.pageScroll.lock();
  4823. if ( this.$el.is( ':visible' ) ) {
  4824. return this;
  4825. }
  4826. // Refresh the preview display
  4827. this.$el.show();
  4828. this.refreshPreview( this.builder.model.getPanelsData() );
  4829. // Move the builder view into the Live Editor
  4830. this.originalContainer = this.builder.$el.parent();
  4831. this.builder.$el.appendTo( this.$( '.so-live-editor-builder' ) );
  4832. this.builder.$( '.so-tool-button.so-live-editor' ).hide();
  4833. this.builder.trigger( 'builder_resize' );
  4834. if( $('#original_post_status' ).val() === 'auto-draft' && ! this.autoSaved ) {
  4835. // The live editor requires a saved draft post, so we'll create one for auto-draft posts
  4836. var thisView = this;
  4837. if ( wp.autosave ) {
  4838. // Set a temporary post title so the autosave triggers properly
  4839. if( $('#title[name="post_title"]' ).val() === '' ) {
  4840. $('#title[name="post_title"]' ).val( panelsOptions.loc.draft ).trigger('keydown');
  4841. }
  4842. $( document ).one( 'heartbeat-tick.autosave', function(){
  4843. thisView.autoSaved = true;
  4844. thisView.refreshPreview( thisView.builder.model.getPanelsData() );
  4845. } );
  4846. wp.autosave.server.triggerSave();
  4847. }
  4848. }
  4849. },
  4850. /**
  4851. * Close the live editor
  4852. */
  4853. close: function () {
  4854. if ( ! this.$el.is( ':visible' ) ) {
  4855. return this;
  4856. }
  4857. this.$el.hide();
  4858. panels.helpers.pageScroll.unlock();
  4859. // Move the builder back to its original container
  4860. this.builder.$el.appendTo( this.originalContainer );
  4861. this.builder.$( '.so-tool-button.so-live-editor' ).show();
  4862. this.builder.trigger( 'builder_resize' );
  4863. },
  4864. /**
  4865. * Collapse the live editor
  4866. */
  4867. collapse: function () {
  4868. this.$el.toggleClass( 'so-collapsed' );
  4869. var text = this.$( '.live-editor-collapse span' );
  4870. text.html( text.data( this.$el.hasClass( 'so-collapsed' ) ? 'expand' : 'collapse' ) );
  4871. },
  4872. /**
  4873. * Create an overlay in the preview.
  4874. *
  4875. * @param over
  4876. * @return {*|Object} The item we're hovering over.
  4877. */
  4878. highlightElement: function ( over ) {
  4879. if( ! _.isUndefined( this.resetHighlightTimeout ) ) {
  4880. clearTimeout( this.resetHighlightTimeout );
  4881. }
  4882. // Remove any old overlays
  4883. var body = this.previewIframe.contents().find( 'body' );
  4884. body.find( '.panel-grid .panel-grid-cell .so-panel' )
  4885. .filter( function () {
  4886. // Filter to only include non nested
  4887. return $( this ).parents( '.so-panel' ).length === 0;
  4888. } )
  4889. .not( over )
  4890. .addClass( 'so-panels-faded' );
  4891. over.removeClass( 'so-panels-faded' ).addClass( 'so-panels-highlighted' );
  4892. },
  4893. /**
  4894. * Reset highlights in the live preview
  4895. */
  4896. resetHighlights: function() {
  4897. var body = this.previewIframe.contents().find( 'body' );
  4898. this.resetHighlightTimeout = setTimeout( function(){
  4899. body.find( '.panel-grid .panel-grid-cell .so-panel' )
  4900. .removeClass( 'so-panels-faded so-panels-highlighted' );
  4901. }, 100 );
  4902. },
  4903. /**
  4904. * Scroll over an element in the live preview
  4905. * @param over
  4906. */
  4907. scrollToElement: function( over ) {
  4908. var contentWindow = this.$( '.so-preview iframe' )[0].contentWindow;
  4909. contentWindow.liveEditorScrollTo( over );
  4910. },
  4911. handleRefreshData: function ( newData, args ) {
  4912. if ( ! this.$el.is( ':visible' ) ) {
  4913. return this;
  4914. }
  4915. this.refreshPreview( newData );
  4916. },
  4917. handleLoadData: function () {
  4918. if ( ! this.$el.is( ':visible' ) ) {
  4919. return this;
  4920. }
  4921. this.refreshPreview( this.builder.model.getPanelsData() );
  4922. },
  4923. /**
  4924. * Refresh the Live Editor preview.
  4925. * @returns {exports}
  4926. */
  4927. refreshPreview: function ( data ) {
  4928. var loadTimePrediction = this.loadTimes.length ?
  4929. _.reduce( this.loadTimes, function ( memo, num ) {
  4930. return memo + num;
  4931. }, 0 ) / this.loadTimes.length : 1000;
  4932. // Store the last preview iframe position
  4933. if( ! _.isNull( this.previewIframe ) ) {
  4934. if ( ! this.$( '.so-preview-overlay' ).is( ':visible' ) ) {
  4935. this.previewScrollTop = this.previewIframe.contents().scrollTop();
  4936. }
  4937. }
  4938. // Add a loading bar
  4939. this.$( '.so-preview-overlay' ).show();
  4940. this.$( '.so-preview-overlay .so-loading-bar' )
  4941. .clearQueue()
  4942. .css( 'width', '0%' )
  4943. .animate( {width: '100%'}, parseInt( loadTimePrediction ) + 100 );
  4944. this.postToIframe(
  4945. {
  4946. live_editor_panels_data: JSON.stringify( data ),
  4947. live_editor_post_ID: this.builder.config.postId
  4948. },
  4949. this.previewUrl,
  4950. this.$('.so-preview')
  4951. );
  4952. this.previewIframe.data( 'load-start', new Date().getTime() );
  4953. },
  4954. /**
  4955. * Use a temporary form to post data to an iframe.
  4956. *
  4957. * @param data The data to send
  4958. * @param url The preview URL
  4959. * @param target The target iframe
  4960. */
  4961. postToIframe: function( data, url, target ){
  4962. // Store the old preview
  4963. if( ! _.isNull( this.previewIframe ) ) {
  4964. this.previewIframe.remove();
  4965. }
  4966. var iframeId = 'siteorigin-panels-live-preview-' + this.previewFrameId;
  4967. // Remove the old preview frame
  4968. this.previewIframe = $('<iframe src="javascript:false;" />')
  4969. .attr( {
  4970. 'id' : iframeId,
  4971. 'name' : iframeId,
  4972. } )
  4973. .appendTo( target )
  4974. this.setupPreviewFrame( this.previewIframe );
  4975. // We can use a normal POST form submit
  4976. var tempForm = $('<form id="soPostToPreviewFrame" method="post" />')
  4977. .attr( {
  4978. id: iframeId,
  4979. target: this.previewIframe.attr('id'),
  4980. action: url
  4981. } )
  4982. .appendTo( 'body' );
  4983. $.each( data, function( name, value ){
  4984. $('<input type="hidden" />')
  4985. .attr( {
  4986. name: name,
  4987. value: value
  4988. } )
  4989. .appendTo( tempForm );
  4990. } );
  4991. tempForm
  4992. .submit()
  4993. .remove();
  4994. this.previewFrameId++;
  4995. return this.previewIframe;
  4996. },
  4997. /**
  4998. * Do all the basic setup for the preview Iframe element
  4999. * @param iframe
  5000. */
  5001. setupPreviewFrame: function( iframe ){
  5002. var thisView = this;
  5003. iframe
  5004. .data( 'iframeready', false )
  5005. .on( 'iframeready', function () {
  5006. var $$ = $( this ),
  5007. $iframeContents = $$.contents();
  5008. if( $$.data( 'iframeready' ) ) {
  5009. // Skip this if the iframeready function has already run
  5010. return;
  5011. }
  5012. $$.data( 'iframeready', true );
  5013. if ( $$.data( 'load-start' ) !== undefined ) {
  5014. thisView.loadTimes.unshift( new Date().getTime() - $$.data( 'load-start' ) );
  5015. if ( ! _.isEmpty( thisView.loadTimes ) ) {
  5016. thisView.loadTimes = thisView.loadTimes.slice( 0, 4 );
  5017. }
  5018. }
  5019. setTimeout( function(){
  5020. // Scroll to the correct position
  5021. $iframeContents.scrollTop( thisView.previewScrollTop );
  5022. thisView.$( '.so-preview-overlay' ).hide();
  5023. }, 100 );
  5024. // Lets find all the first level grids. This is to account for the Page Builder layout widget.
  5025. var layoutWrapper = $iframeContents.find( '#pl-' + thisView.builder.config.postId );
  5026. layoutWrapper.find( '.panel-grid .panel-grid-cell .so-panel' )
  5027. .filter( function () {
  5028. // Filter to only include non nested
  5029. return $( this ).closest( '.panel-layout' ).is( layoutWrapper );
  5030. } )
  5031. .each( function ( i, el ) {
  5032. var $$ = $( el );
  5033. var widgetEdit = thisView.$( '.so-live-editor-builder .so-widget-wrapper' ).eq( $$.data( 'index' ) );
  5034. widgetEdit.data( 'live-editor-preview-widget', $$ );
  5035. $$
  5036. .css( {
  5037. 'cursor': 'pointer'
  5038. } )
  5039. .mouseenter( function () {
  5040. widgetEdit.parent().addClass( 'so-hovered' );
  5041. thisView.highlightElement( $$ );
  5042. } )
  5043. .mouseleave( function () {
  5044. widgetEdit.parent().removeClass( 'so-hovered' );
  5045. thisView.resetHighlights();
  5046. } )
  5047. .click( function ( e ) {
  5048. e.preventDefault();
  5049. // When we click a widget, send that click to the form
  5050. widgetEdit.find( '.title h4' ).click();
  5051. } );
  5052. } );
  5053. // Prevent default clicks inside the preview iframe
  5054. $iframeContents.find( "a" ).css( {'pointer-events': 'none'} ).click( function ( e ) {
  5055. e.preventDefault();
  5056. } );
  5057. } )
  5058. .on( 'load', function(){
  5059. var $$ = $( this );
  5060. if( ! $$.data( 'iframeready' ) ) {
  5061. $$.trigger('iframeready');
  5062. }
  5063. } );
  5064. },
  5065. /**
  5066. * Return true if the live editor has a valid preview URL.
  5067. * @return {boolean}
  5068. */
  5069. hasPreviewUrl: function () {
  5070. return this.$( 'form.live-editor-form' ).attr( 'action' ) !== '';
  5071. },
  5072. /**
  5073. * Toggle the size of the preview iframe to simulate mobile devices.
  5074. * @param e
  5075. */
  5076. mobileToggle: function( e ){
  5077. var button = $( e.currentTarget );
  5078. this.$('.live-editor-mode' ).not( button ).removeClass('so-active');
  5079. button.addClass( 'so-active' );
  5080. this.$el
  5081. .removeClass( 'live-editor-desktop-mode live-editor-tablet-mode live-editor-mobile-mode' )
  5082. .addClass( 'live-editor-' + button.data( 'mode' ) + '-mode' );
  5083. }
  5084. } );
  5085. },{}],27:[function(require,module,exports){
  5086. var panels = window.panels, $ = jQuery;
  5087. module.exports = Backbone.View.extend( {
  5088. template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-row' ).html() ) ),
  5089. events: {
  5090. 'click .so-row-settings': 'editSettingsHandler',
  5091. 'click .so-row-duplicate': 'duplicateHandler',
  5092. 'click .so-row-delete': 'confirmedDeleteHandler',
  5093. 'click .so-row-color': 'rowColorChangeHandler',
  5094. },
  5095. builder: null,
  5096. dialog: null,
  5097. /**
  5098. * Initialize the row view
  5099. */
  5100. initialize: function () {
  5101. var rowCells = this.model.get('cells');
  5102. rowCells.on( 'add', this.handleCellAdd, this );
  5103. rowCells.on( 'remove', this.handleCellRemove, this );
  5104. this.model.on( 'reweight_cells', this.resize, this );
  5105. this.model.on( 'destroy', this.onModelDestroy, this );
  5106. this.model.on( 'visual_destroy', this.visualDestroyModel, this );
  5107. var thisView = this;
  5108. rowCells.each( function ( cell ) {
  5109. thisView.listenTo( cell.get('widgets'), 'add', thisView.resize );
  5110. } );
  5111. // When ever a new cell is added, listen to it for new widgets
  5112. rowCells.on( 'add', function ( cell ) {
  5113. thisView.listenTo( cell.get('widgets'), 'add', thisView.resize );
  5114. }, this );
  5115. this.model.on( 'change:label', this.onLabelChange, this );
  5116. },
  5117. /**
  5118. * Render the row.
  5119. *
  5120. * @returns {panels.view.row}
  5121. */
  5122. render: function () {
  5123. var rowColorLabel = this.model.has( 'color_label' ) ? this.model.get( 'color_label' ) : 1;
  5124. var rowLabel = this.model.has( 'label' ) ? this.model.get( 'label' ) : '';
  5125. this.setElement( this.template( { rowColorLabel: rowColorLabel, rowLabel: rowLabel } ) );
  5126. this.$el.data( 'view', this );
  5127. // Create views for the cells in this row
  5128. var thisView = this;
  5129. this.model.get('cells').each( function ( cell ) {
  5130. var cellView = new panels.view.cell( {
  5131. model: cell
  5132. } );
  5133. cellView.row = thisView;
  5134. cellView.render();
  5135. cellView.$el.appendTo( thisView.$( '.so-cells' ) );
  5136. } );
  5137. // Remove any unsupported actions
  5138. if( ! this.builder.supports( 'rowAction' ) ) {
  5139. this.$('.so-row-toolbar .so-dropdown-wrapper' ).remove();
  5140. this.$el.addClass('so-row-no-actions');
  5141. }
  5142. else {
  5143. if( ! this.builder.supports( 'editWidget' ) ) {
  5144. this.$('.so-row-toolbar .so-row-settings' ).parent().remove();
  5145. this.$el.addClass('so-row-no-edit');
  5146. }
  5147. if( ! this.builder.supports( 'addWidget' ) ) {
  5148. this.$('.so-row-toolbar .so-row-duplicate' ).parent().remove();
  5149. this.$el.addClass('so-row-no-duplicate');
  5150. }
  5151. if( ! this.builder.supports( 'deleteWidget' ) ) {
  5152. this.$('.so-row-toolbar .so-row-delete' ).parent().remove();
  5153. this.$el.addClass('so-row-no-delete');
  5154. }
  5155. }
  5156. if( ! this.builder.supports( 'moveRow' ) ) {
  5157. this.$('.so-row-toolbar .so-row-move' ).remove();
  5158. this.$el.addClass('so-row-no-move');
  5159. }
  5160. if( !$.trim( this.$('.so-row-toolbar').html() ).length ) {
  5161. this.$('.so-row-toolbar' ).remove();
  5162. }
  5163. // Resize the rows when ever the widget sortable moves
  5164. this.builder.on( 'widget_sortable_move', this.resize, this );
  5165. this.builder.on( 'builder_resize', this.resize, this );
  5166. this.resize();
  5167. return this;
  5168. },
  5169. /**
  5170. * Give a visual indication of the creation of this row
  5171. */
  5172. visualCreate: function () {
  5173. this.$el.hide().fadeIn( 'fast' );
  5174. },
  5175. /**
  5176. * Visually resize the row so that all cell heights are the same and the widths so that they balance to 100%
  5177. *
  5178. * @param e
  5179. */
  5180. resize: function ( e ) {
  5181. // Don't resize this
  5182. if ( ! this.$el.is( ':visible' ) ) {
  5183. return;
  5184. }
  5185. // Reset everything to have an automatic height
  5186. this.$( '.so-cells .cell-wrapper' ).css( 'min-height', 0 );
  5187. this.$( '.so-cells .resize-handle' ).css( 'height', 0 );
  5188. // We'll tie the values to the row view, to prevent issue with values going to different rows
  5189. var height = 0;
  5190. this.$( '.so-cells .cell' ).each( function () {
  5191. height = Math.max(
  5192. height,
  5193. $( this ).height()
  5194. );
  5195. $( this ).css(
  5196. 'width',
  5197. ( $( this ).data( 'view' ).model.get( 'weight' ) * 100) + "%"
  5198. );
  5199. } );
  5200. // Resize all the grids and cell wrappers
  5201. this.$( '.so-cells .cell-wrapper' ).css( 'min-height', Math.max( height, 63 ) );
  5202. this.$( '.so-cells .resize-handle' ).css( 'height', this.$( '.so-cells .cell-wrapper' ).outerHeight() );
  5203. },
  5204. /**
  5205. * Remove the view from the dom.
  5206. */
  5207. onModelDestroy: function () {
  5208. this.remove();
  5209. },
  5210. /**
  5211. * Fade out the view and destroy the model
  5212. */
  5213. visualDestroyModel: function () {
  5214. this.builder.addHistoryEntry( 'row_deleted' );
  5215. var thisView = this;
  5216. this.$el.fadeOut( 'normal', function () {
  5217. thisView.model.destroy();
  5218. thisView.builder.model.refreshPanelsData();
  5219. } );
  5220. },
  5221. onLabelChange: function( model, text ) {
  5222. if ( this.$('.so-row-label').length == 0 ) {
  5223. this.$( '.so-row-toolbar' ).prepend( '<h3 class="so-row-label">' + text + '</h3>' );
  5224. } else {
  5225. this.$('.so-row-label').text( text );
  5226. }
  5227. },
  5228. /**
  5229. * Duplicate this row.
  5230. *
  5231. * @return {boolean}
  5232. */
  5233. duplicateHandler: function () {
  5234. this.builder.addHistoryEntry( 'row_duplicated' );
  5235. var duplicateRow = this.model.clone( this.builder.model );
  5236. this.builder.model.get('rows').add( duplicateRow, {
  5237. at: this.builder.model.get('rows').indexOf( this.model ) + 1
  5238. } );
  5239. this.builder.model.refreshPanelsData();
  5240. },
  5241. /**
  5242. * Copy the row to a localStorage
  5243. */
  5244. copyHandler: function(){
  5245. panels.helpers.clipboard.setModel( this.model );
  5246. },
  5247. /**
  5248. * Create a new row and insert it
  5249. */
  5250. pasteHandler: function(){
  5251. var pastedModel = panels.helpers.clipboard.getModel( 'row-model' );
  5252. if( ! _.isEmpty( pastedModel ) && pastedModel instanceof panels.model.row ) {
  5253. this.builder.addHistoryEntry( 'row_pasted' );
  5254. pastedModel.builder = this.builder.model;
  5255. this.builder.model.get('rows').add( pastedModel, {
  5256. at: this.builder.model.get('rows').indexOf( this.model ) + 1
  5257. } );
  5258. this.builder.model.refreshPanelsData();
  5259. }
  5260. },
  5261. /**
  5262. * Handles deleting the row with a confirmation.
  5263. */
  5264. confirmedDeleteHandler: function ( e ) {
  5265. var $$ = $( e.target );
  5266. // The user clicked on the dashicon
  5267. if ( $$.hasClass( 'dashicons' ) ) {
  5268. $$ = $.parent();
  5269. }
  5270. if ( $$.hasClass( 'so-confirmed' ) ) {
  5271. this.visualDestroyModel();
  5272. } else {
  5273. var originalText = $$.html();
  5274. $$.addClass( 'so-confirmed' ).html(
  5275. '<span class="dashicons dashicons-yes"></span>' + panelsOptions.loc.dropdown_confirm
  5276. );
  5277. setTimeout( function () {
  5278. $$.removeClass( 'so-confirmed' ).html( originalText );
  5279. }, 2500 );
  5280. }
  5281. },
  5282. /**
  5283. * Handle displaying the settings dialog
  5284. */
  5285. editSettingsHandler: function () {
  5286. // Lets open up an instance of the settings dialog
  5287. if ( this.dialog === null ) {
  5288. // Create the dialog
  5289. this.dialog = new panels.dialog.row();
  5290. this.dialog.setBuilder( this.builder ).setRowModel( this.model );
  5291. }
  5292. this.dialog.openDialog();
  5293. return this;
  5294. },
  5295. /**
  5296. * Handle deleting this entire row.
  5297. */
  5298. deleteHandler: function () {
  5299. this.model.destroy();
  5300. return this;
  5301. },
  5302. /**
  5303. * Change the row background color.
  5304. */
  5305. rowColorChangeHandler: function ( event ) {
  5306. this.$( '.so-row-color' ).removeClass( 'so-row-color-selected' );
  5307. var clickedColorElem = $( event.target );
  5308. var newColorLabel = clickedColorElem.data( 'color-label' );
  5309. var oldColorLabel = this.model.has( 'color_label' ) ? this.model.get( 'color_label' ) : 1;
  5310. clickedColorElem.addClass( 'so-row-color-selected' );
  5311. this.$el.removeClass( 'so-row-color-' + oldColorLabel );
  5312. this.$el.addClass( 'so-row-color-' + newColorLabel );
  5313. this.model.set( 'color_label', newColorLabel );
  5314. },
  5315. /**
  5316. * Handle a new cell being added to this row view. For now we'll assume the new cell is always last
  5317. */
  5318. handleCellAdd: function ( cell ) {
  5319. var cellView = new panels.view.cell( {
  5320. model: cell
  5321. } );
  5322. cellView.row = this;
  5323. cellView.render();
  5324. cellView.$el.appendTo( this.$( '.so-cells' ) );
  5325. },
  5326. /**
  5327. * Handle a cell being removed from this row view
  5328. */
  5329. handleCellRemove: function ( cell ) {
  5330. // Find the view that ties in to the cell we're removing
  5331. this.$( '.so-cells > .cell' ).each( function () {
  5332. var view = $( this ).data( 'view' );
  5333. if ( _.isUndefined( view ) ) {
  5334. return;
  5335. }
  5336. if ( view.model.cid === cell.cid ) {
  5337. // Remove this view
  5338. view.remove();
  5339. }
  5340. } );
  5341. },
  5342. /**
  5343. * Build up the contextual menu for a row
  5344. *
  5345. * @param e
  5346. * @param menu
  5347. */
  5348. buildContextualMenu: function ( e, menu ) {
  5349. var options = [];
  5350. for ( var i = 1; i < 5; i ++ ) {
  5351. options.push( {
  5352. title: i + ' ' + panelsOptions.loc.contextual.column
  5353. } );
  5354. }
  5355. if( this.builder.supports( 'addRow' ) ) {
  5356. menu.addSection(
  5357. 'add-row',
  5358. {
  5359. sectionTitle: panelsOptions.loc.contextual.add_row,
  5360. search: false
  5361. },
  5362. options,
  5363. function ( c ) {
  5364. this.builder.addHistoryEntry( 'row_added' );
  5365. var columns = Number( c ) + 1;
  5366. var weights = [];
  5367. for ( var i = 0; i < columns; i ++ ) {
  5368. weights.push( {weight: 100 / columns } );
  5369. }
  5370. // Create the actual row
  5371. var newRow = new panels.model.row( {
  5372. collection: this.collection
  5373. } );
  5374. var cells = new panels.collection.cells(weights);
  5375. cells.each(function (cell) {
  5376. cell.row = newRow;
  5377. });
  5378. newRow.setCells(cells);
  5379. newRow.builder = this.builder.model;
  5380. this.builder.model.get('rows').add( newRow, {
  5381. at: this.builder.model.get('rows').indexOf( this.model ) + 1
  5382. } );
  5383. this.builder.model.refreshPanelsData();
  5384. }.bind( this )
  5385. );
  5386. }
  5387. var actions = {};
  5388. if( this.builder.supports( 'editRow' ) ) {
  5389. actions.edit = { title: panelsOptions.loc.contextual.row_edit };
  5390. }
  5391. // Copy and paste functions
  5392. if ( panels.helpers.clipboard.canCopyPaste() ) {
  5393. actions.copy = { title: panelsOptions.loc.contextual.row_copy };
  5394. if ( this.builder.supports( 'addRow' ) && panels.helpers.clipboard.isModel( 'row-model' ) ) {
  5395. actions.paste = { title: panelsOptions.loc.contextual.row_paste };
  5396. }
  5397. }
  5398. if( this.builder.supports( 'addRow' ) ) {
  5399. actions.duplicate = { title: panelsOptions.loc.contextual.row_duplicate };
  5400. }
  5401. if( this.builder.supports( 'deleteRow' ) ) {
  5402. actions.delete = { title: panelsOptions.loc.contextual.row_delete, confirm: true };
  5403. }
  5404. if( ! _.isEmpty( actions ) ) {
  5405. menu.addSection(
  5406. 'row-actions',
  5407. {
  5408. sectionTitle: panelsOptions.loc.contextual.row_actions,
  5409. search: false,
  5410. },
  5411. actions,
  5412. function ( c ) {
  5413. switch ( c ) {
  5414. case 'edit':
  5415. this.editSettingsHandler();
  5416. break;
  5417. case 'copy':
  5418. this.copyHandler();
  5419. break;
  5420. case 'paste':
  5421. this.pasteHandler();
  5422. break;
  5423. case 'duplicate':
  5424. this.duplicateHandler();
  5425. break;
  5426. case 'delete':
  5427. this.visualDestroyModel();
  5428. break;
  5429. }
  5430. }.bind( this )
  5431. );
  5432. }
  5433. },
  5434. } );
  5435. },{}],28:[function(require,module,exports){
  5436. var panels = window.panels, $ = jQuery;
  5437. module.exports = Backbone.View.extend( {
  5438. stylesLoaded: false,
  5439. initialize: function () {
  5440. },
  5441. /**
  5442. * Render the visual styles object.
  5443. *
  5444. * @param type
  5445. * @param postId
  5446. */
  5447. render: function ( stylesType, postId, args ) {
  5448. if ( _.isUndefined( stylesType ) ) {
  5449. return;
  5450. }
  5451. // Add in the default args
  5452. args = _.extend( {
  5453. builderType: '',
  5454. dialog: null
  5455. }, args );
  5456. this.$el.addClass( 'so-visual-styles so-' + stylesType + '-styles' );
  5457. var postArgs = {
  5458. builderType: args.builderType
  5459. };
  5460. if ( stylesType === 'cell') {
  5461. postArgs.index = args.index;
  5462. }
  5463. // Load the form
  5464. $.post(
  5465. panelsOptions.ajaxurl,
  5466. {
  5467. action: 'so_panels_style_form',
  5468. type: stylesType,
  5469. style: this.model.get( 'style' ),
  5470. args: JSON.stringify( postArgs ),
  5471. postId: postId
  5472. },
  5473. function ( response ) {
  5474. this.$el.html( response );
  5475. this.setupFields();
  5476. this.stylesLoaded = true;
  5477. this.trigger( 'styles_loaded', ! _.isEmpty( response ) );
  5478. if ( ! _.isNull( args.dialog ) ) {
  5479. args.dialog.trigger( 'styles_loaded', ! _.isEmpty( response ) );
  5480. }
  5481. }.bind(this)
  5482. );
  5483. return this;
  5484. },
  5485. /**
  5486. * Attach the style view to the DOM.
  5487. *
  5488. * @param wrapper
  5489. */
  5490. attach: function ( wrapper ) {
  5491. wrapper.append( this.$el );
  5492. },
  5493. /**
  5494. * Detach the styles view from the DOM
  5495. */
  5496. detach: function () {
  5497. this.$el.detach();
  5498. },
  5499. /**
  5500. * Setup all the fields
  5501. */
  5502. setupFields: function () {
  5503. // Set up the sections as collapsible
  5504. this.$( '.style-section-wrapper' ).each( function () {
  5505. var $s = $( this );
  5506. $s.find( '.style-section-head' ).click( function ( e ) {
  5507. e.preventDefault();
  5508. $s.find( '.style-section-fields' ).slideToggle( 'fast' );
  5509. } );
  5510. } );
  5511. // Set up the color fields
  5512. if ( ! _.isUndefined( $.fn.wpColorPicker ) ) {
  5513. if ( _.isObject( panelsOptions.wpColorPickerOptions.palettes ) && ! $.isArray( panelsOptions.wpColorPickerOptions.palettes ) ) {
  5514. panelsOptions.wpColorPickerOptions.palettes = $.map( panelsOptions.wpColorPickerOptions.palettes, function ( el ) {
  5515. return el;
  5516. } );
  5517. }
  5518. this.$( '.so-wp-color-field' ).wpColorPicker( panelsOptions.wpColorPickerOptions );
  5519. }
  5520. // Set up the image select fields
  5521. this.$( '.style-field-image' ).each( function () {
  5522. var frame = null;
  5523. var $s = $( this );
  5524. $s.find( '.so-image-selector' ).click( function ( e ) {
  5525. e.preventDefault();
  5526. if ( frame === null ) {
  5527. // Create the media frame.
  5528. frame = wp.media( {
  5529. // Set the title of the modal.
  5530. title: 'choose',
  5531. // Tell the modal to show only images.
  5532. library: {
  5533. type: 'image'
  5534. },
  5535. // Customize the submit button.
  5536. button: {
  5537. // Set the text of the button.
  5538. text: 'Done',
  5539. close: true
  5540. }
  5541. } );
  5542. frame.on( 'select', function () {
  5543. var attachment = frame.state().get( 'selection' ).first().attributes;
  5544. var url = attachment.url;
  5545. if ( ! _.isUndefined( attachment.sizes ) ) {
  5546. try {
  5547. url = attachment.sizes.thumbnail.url;
  5548. }
  5549. catch ( e ) {
  5550. // We'll use the full image instead
  5551. url = attachment.sizes.full.url;
  5552. }
  5553. }
  5554. $s.find( '.current-image' ).css( 'background-image', 'url(' + url + ')' );
  5555. // Store the ID
  5556. $s.find( 'input' ).val( attachment.id )
  5557. } );
  5558. }
  5559. frame.open();
  5560. } );
  5561. // Handle clicking on remove
  5562. $s.find( '.remove-image' ).click( function ( e ) {
  5563. e.preventDefault();
  5564. $s.find( '.current-image' ).css( 'background-image', 'none' );
  5565. $s.find( 'input' ).val( '' );
  5566. } );
  5567. } );
  5568. // Set up all the measurement fields
  5569. this.$( '.style-field-measurement' ).each( function () {
  5570. var $$ = $( this );
  5571. var text = $$.find( 'input[type="text"]' );
  5572. var unit = $$.find( 'select' );
  5573. var hidden = $$.find( 'input[type="hidden"]' );
  5574. text.focus( function(){
  5575. $(this).select();
  5576. } );
  5577. /**
  5578. * Load value into the visible input fields.
  5579. * @param value
  5580. */
  5581. var loadValue = function( value ) {
  5582. if( value === '' ) {
  5583. return;
  5584. }
  5585. var re = /(?:([0-9\.,\-]+)(.*))+/;
  5586. var valueList = hidden.val().split( ' ' );
  5587. var valueListValue = [];
  5588. for ( var i in valueList ) {
  5589. var match = re.exec( valueList[i] );
  5590. if ( ! _.isNull( match ) && ! _.isUndefined( match[1] ) && ! _.isUndefined( match[2] ) ) {
  5591. valueListValue.push( match[1] );
  5592. unit.val( match[2] );
  5593. }
  5594. }
  5595. if( text.length === 1 ) {
  5596. // This is a single input text field
  5597. text.val( valueListValue.join( ' ' ) );
  5598. }
  5599. else {
  5600. // We're dealing with a multiple field
  5601. if( valueListValue.length === 1 ) {
  5602. valueListValue = [ valueListValue[0], valueListValue[0], valueListValue[0], valueListValue[0] ];
  5603. }
  5604. else if( valueListValue.length === 2 ) {
  5605. valueListValue = [ valueListValue[0], valueListValue[1], valueListValue[0], valueListValue[1] ];
  5606. }
  5607. else if( valueListValue.length === 3 ) {
  5608. valueListValue = [ valueListValue[0], valueListValue[1], valueListValue[2], valueListValue[1] ];
  5609. }
  5610. // Store this in the visible fields
  5611. text.each( function( i, el ) {
  5612. $( el ).val( valueListValue[i] );
  5613. } );
  5614. }
  5615. };
  5616. loadValue( hidden.val() );
  5617. /**
  5618. * Set value of the hidden field based on inputs
  5619. */
  5620. var setValue = function( e ){
  5621. var i;
  5622. if( text.length === 1 ) {
  5623. // We're dealing with a single measurement
  5624. var fullString = text
  5625. .val()
  5626. .split( ' ' )
  5627. .filter( function ( value ) {
  5628. return value !== '';
  5629. } )
  5630. .map( function ( value ) {
  5631. return value + unit.val();
  5632. } )
  5633. .join( ' ' );
  5634. hidden.val( fullString );
  5635. }
  5636. else {
  5637. var target = $( e.target ),
  5638. valueList = [],
  5639. emptyIndex = [],
  5640. fullIndex = [];
  5641. text.each( function( i, el ) {
  5642. var value = $( el ).val( ) !== '' ? parseFloat( $( el ).val( ) ) : null;
  5643. valueList.push( value );
  5644. if( value === null ) {
  5645. emptyIndex.push( i );
  5646. }
  5647. else {
  5648. fullIndex.push( i );
  5649. }
  5650. } );
  5651. if( emptyIndex.length === 3 && fullIndex[0] === text.index( target ) ) {
  5652. text.val( target.val() );
  5653. valueList = [ target.val(), target.val(), target.val(), target.val() ];
  5654. }
  5655. if( JSON.stringify( valueList ) === JSON.stringify( [ null, null, null, null ] ) ) {
  5656. hidden.val('');
  5657. }
  5658. else {
  5659. hidden.val( valueList.map( function( k ){
  5660. return ( k === null ? 0 : k ) + unit.val();
  5661. } ).join( ' ' ) );
  5662. }
  5663. }
  5664. };
  5665. // Set the value when ever anything changes
  5666. text.change( setValue );
  5667. unit.change( setValue );
  5668. } );
  5669. }
  5670. } );
  5671. },{}],29:[function(require,module,exports){
  5672. var panels = window.panels, $ = jQuery;
  5673. module.exports = Backbone.View.extend( {
  5674. template: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-builder-widget' ).html() ) ),
  5675. // The cell view that this widget belongs to
  5676. cell: null,
  5677. // The edit dialog
  5678. dialog: null,
  5679. events: {
  5680. 'click .widget-edit': 'editHandler',
  5681. 'click .title h4': 'titleClickHandler',
  5682. 'click .actions .widget-duplicate': 'duplicateHandler',
  5683. 'click .actions .widget-delete': 'deleteHandler'
  5684. },
  5685. /**
  5686. * Initialize the widget
  5687. */
  5688. initialize: function () {
  5689. this.model.on( 'user_edit', this.editHandler, this ); // When a user wants to edit the widget model
  5690. this.model.on( 'user_duplicate', this.duplicateHandler, this ); // When a user wants to duplicate the widget model
  5691. this.model.on( 'destroy', this.onModelDestroy, this );
  5692. this.model.on( 'visual_destroy', this.visualDestroyModel, this );
  5693. this.model.on( 'change:values', this.onModelChange, this );
  5694. this.model.on( 'change:label', this.onLabelChange, this );
  5695. },
  5696. /**
  5697. * Render the widget
  5698. */
  5699. render: function ( options ) {
  5700. options = _.extend( {'loadForm': false}, options );
  5701. this.setElement( this.template( {
  5702. title: this.model.getWidgetField( 'title' ),
  5703. description: this.model.getTitle()
  5704. } ) );
  5705. this.$el.data( 'view', this );
  5706. // Remove any unsupported actions
  5707. if( ! this.cell.row.builder.supports( 'editWidget' ) || this.model.get( 'read_only' ) ) {
  5708. this.$( '.actions .widget-edit' ).remove();
  5709. this.$el.addClass('so-widget-no-edit');
  5710. }
  5711. if( ! this.cell.row.builder.supports( 'addWidget' ) ) {
  5712. this.$( '.actions .widget-duplicate' ).remove();
  5713. this.$el.addClass('so-widget-no-duplicate');
  5714. }
  5715. if( ! this.cell.row.builder.supports( 'deleteWidget' ) ) {
  5716. this.$( '.actions .widget-delete' ).remove();
  5717. this.$el.addClass('so-widget-no-delete');
  5718. }
  5719. if( ! this.cell.row.builder.supports( 'moveWidget' ) ) {
  5720. this.$el.addClass('so-widget-no-move');
  5721. }
  5722. if( !$.trim( this.$('.actions').html() ).length ) {
  5723. this.$( '.actions' ).remove();
  5724. }
  5725. if( this.model.get( 'read_only' ) ) {
  5726. this.$el.addClass('so-widget-read-only');
  5727. }
  5728. if ( _.size( this.model.get( 'values' ) ) === 0 || options.loadForm ) {
  5729. // If this widget doesn't have a value, create a form and save it
  5730. var dialog = this.getEditDialog();
  5731. // Save the widget as soon as the form is loaded
  5732. dialog.once( 'form_loaded', dialog.saveWidget, dialog );
  5733. // Setup the dialog to load the form
  5734. dialog.setupDialog();
  5735. }
  5736. return this;
  5737. },
  5738. /**
  5739. * Display an animation that implies creation using a visual animation
  5740. */
  5741. visualCreate: function () {
  5742. this.$el.hide().fadeIn( 'fast' );
  5743. },
  5744. /**
  5745. * Get the dialog view of the form that edits this widget
  5746. *
  5747. * @returns {null}
  5748. */
  5749. getEditDialog: function () {
  5750. if ( this.dialog === null ) {
  5751. this.dialog = new panels.dialog.widget( {
  5752. model: this.model
  5753. } );
  5754. this.dialog.setBuilder( this.cell.row.builder );
  5755. // Store the widget view
  5756. this.dialog.widgetView = this;
  5757. }
  5758. return this.dialog;
  5759. },
  5760. /**
  5761. * Handle clicking on edit widget.
  5762. *
  5763. * @returns {boolean}
  5764. */
  5765. editHandler: function () {
  5766. // Create a new dialog for editing this
  5767. this.getEditDialog().openDialog();
  5768. },
  5769. titleClickHandler: function( event ){
  5770. if ( ! this.cell.row.builder.supports( 'editWidget' ) || this.model.get( 'read_only' ) ) {
  5771. return this;
  5772. }
  5773. this.editHandler();
  5774. return this;
  5775. },
  5776. /**
  5777. * Handle clicking on duplicate.
  5778. *
  5779. * @returns {boolean}
  5780. */
  5781. duplicateHandler: function () {
  5782. // Add the history entry
  5783. this.cell.row.builder.addHistoryEntry( 'widget_duplicated' );
  5784. // Create the new widget and connect it to the widget collection for the current row
  5785. var newWidget = this.model.clone( this.model.cell );
  5786. this.cell.model.get('widgets').add( newWidget, {
  5787. // Add this after the existing model
  5788. at: this.model.collection.indexOf( this.model ) + 1
  5789. } );
  5790. this.cell.row.builder.model.refreshPanelsData();
  5791. return this;
  5792. },
  5793. /**
  5794. * Copy the row to a cookie based clipboard
  5795. */
  5796. copyHandler: function(){
  5797. panels.helpers.clipboard.setModel( this.model );
  5798. },
  5799. /**
  5800. * Handle clicking on delete.
  5801. *
  5802. * @returns {boolean}
  5803. */
  5804. deleteHandler: function () {
  5805. this.model.trigger( 'visual_destroy' );
  5806. return this;
  5807. },
  5808. onModelChange: function () {
  5809. // Update the description when ever the model changes
  5810. this.$( '.description' ).html( this.model.getTitle() );
  5811. },
  5812. onLabelChange: function( model ) {
  5813. this.$( '.title > h4' ).text( model.getWidgetField( 'title' ) );
  5814. },
  5815. /**
  5816. * When the model is destroyed, fade it out
  5817. */
  5818. onModelDestroy: function () {
  5819. this.remove();
  5820. },
  5821. /**
  5822. * Visually destroy a model
  5823. */
  5824. visualDestroyModel: function () {
  5825. // Add the history entry
  5826. this.cell.row.builder.addHistoryEntry( 'widget_deleted' );
  5827. var thisView = this;
  5828. this.$el.fadeOut( 'fast', function () {
  5829. thisView.cell.row.resize();
  5830. thisView.model.destroy();
  5831. thisView.cell.row.builder.model.refreshPanelsData();
  5832. thisView.remove();
  5833. } );
  5834. return this;
  5835. },
  5836. /**
  5837. * Build up the contextual menu for a widget
  5838. *
  5839. * @param e
  5840. * @param menu
  5841. */
  5842. buildContextualMenu: function ( e, menu ) {
  5843. if( this.cell.row.builder.supports( 'addWidget' ) ) {
  5844. menu.addSection(
  5845. 'add-widget-below',
  5846. {
  5847. sectionTitle: panelsOptions.loc.contextual.add_widget_below,
  5848. searchPlaceholder: panelsOptions.loc.contextual.search_widgets,
  5849. defaultDisplay: panelsOptions.contextual.default_widgets
  5850. },
  5851. panelsOptions.widgets,
  5852. function ( c ) {
  5853. this.cell.row.builder.addHistoryEntry( 'widget_added' );
  5854. var widget = new panels.model.widget( {
  5855. class: c
  5856. } );
  5857. widget.cell = this.cell.model;
  5858. // Insert the new widget below
  5859. this.cell.model.get('widgets').add( widget, {
  5860. // Add this after the existing model
  5861. at: this.model.collection.indexOf( this.model ) + 1
  5862. } );
  5863. this.cell.row.builder.model.refreshPanelsData();
  5864. }.bind( this )
  5865. );
  5866. }
  5867. var actions = {};
  5868. if( this.cell.row.builder.supports( 'editWidget' ) && ! this.model.get( 'read_only' ) ) {
  5869. actions.edit = { title: panelsOptions.loc.contextual.widget_edit };
  5870. }
  5871. // Copy and paste functions
  5872. if ( panels.helpers.clipboard.canCopyPaste() ) {
  5873. actions.copy = {title: panelsOptions.loc.contextual.widget_copy};
  5874. }
  5875. if( this.cell.row.builder.supports( 'addWidget' ) ) {
  5876. actions.duplicate = { title: panelsOptions.loc.contextual.widget_duplicate };
  5877. }
  5878. if( this.cell.row.builder.supports( 'deleteWidget' ) ) {
  5879. actions.delete = { title: panelsOptions.loc.contextual.widget_delete, confirm: true };
  5880. }
  5881. if( ! _.isEmpty( actions ) ) {
  5882. menu.addSection(
  5883. 'widget-actions',
  5884. {
  5885. sectionTitle: panelsOptions.loc.contextual.widget_actions,
  5886. search: false,
  5887. },
  5888. actions,
  5889. function ( c ) {
  5890. switch ( c ) {
  5891. case 'edit':
  5892. this.editHandler();
  5893. break;
  5894. case 'copy':
  5895. this.copyHandler();
  5896. break;
  5897. case 'duplicate':
  5898. this.duplicateHandler();
  5899. break;
  5900. case 'delete':
  5901. this.visualDestroyModel();
  5902. break;
  5903. }
  5904. }.bind( this )
  5905. );
  5906. }
  5907. // Lets also add the contextual menu for the entire row
  5908. this.cell.buildContextualMenu( e, menu );
  5909. }
  5910. } );
  5911. },{}],30:[function(require,module,exports){
  5912. var $ = jQuery;
  5913. var customHtmlWidget = {
  5914. addWidget: function( idBase, widgetContainer, widgetId ) {
  5915. var component = wp.customHtmlWidgets;
  5916. var fieldContainer = $( '<div></div>' );
  5917. var syncContainer = widgetContainer.find( '.widget-content:first' );
  5918. syncContainer.before( fieldContainer );
  5919. var widgetControl = new component.CustomHtmlWidgetControl( {
  5920. el: fieldContainer,
  5921. syncContainer: syncContainer,
  5922. } );
  5923. widgetControl.initializeEditor();
  5924. // HACK: To ensure CodeMirror resize for the gutter.
  5925. widgetControl.editor.codemirror.refresh();
  5926. return widgetControl;
  5927. }
  5928. };
  5929. module.exports = customHtmlWidget;
  5930. },{}],31:[function(require,module,exports){
  5931. var customHtmlWidget = require( './custom-html-widget' );
  5932. var mediaWidget = require( './media-widget' );
  5933. var textWidget = require( './text-widget' );
  5934. var jsWidget = {
  5935. CUSTOM_HTML: 'custom_html',
  5936. MEDIA_AUDIO: 'media_audio',
  5937. MEDIA_GALLERY: 'media_gallery',
  5938. MEDIA_IMAGE: 'media_image',
  5939. MEDIA_VIDEO: 'media_video',
  5940. TEXT: 'text',
  5941. addWidget: function( widgetContainer, widgetId ) {
  5942. var idBase = widgetContainer.find( '> .id_base' ).val();
  5943. var widget;
  5944. switch ( idBase ) {
  5945. case this.CUSTOM_HTML:
  5946. widget = customHtmlWidget;
  5947. break;
  5948. case this.MEDIA_AUDIO:
  5949. case this.MEDIA_GALLERY:
  5950. case this.MEDIA_IMAGE:
  5951. case this.MEDIA_VIDEO:
  5952. widget = mediaWidget;
  5953. break;
  5954. case this.TEXT:
  5955. widget = textWidget;
  5956. break
  5957. }
  5958. widget.addWidget( idBase, widgetContainer, widgetId );
  5959. },
  5960. };
  5961. module.exports = jsWidget;
  5962. },{"./custom-html-widget":30,"./media-widget":32,"./text-widget":33}],32:[function(require,module,exports){
  5963. var $ = jQuery;
  5964. var mediaWidget = {
  5965. addWidget: function( idBase, widgetContainer, widgetId ) {
  5966. var component = wp.mediaWidgets;
  5967. var ControlConstructor = component.controlConstructors[ idBase ];
  5968. if ( ! ControlConstructor ) {
  5969. return;
  5970. }
  5971. var ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel;
  5972. var syncContainer = widgetContainer.find( '> .widget-content' );
  5973. var controlContainer = $( '<div class="media-widget-control"></div>' );
  5974. syncContainer.before( controlContainer );
  5975. var modelAttributes = {};
  5976. syncContainer.find( '.media-widget-instance-property' ).each( function() {
  5977. var input = $( this );
  5978. modelAttributes[ input.data( 'property' ) ] = input.val();
  5979. });
  5980. modelAttributes.widget_id = widgetId;
  5981. var widgetModel = new ModelConstructor( modelAttributes );
  5982. var widgetControl = new ControlConstructor({
  5983. el: controlContainer,
  5984. syncContainer: syncContainer,
  5985. model: widgetModel,
  5986. });
  5987. widgetControl.render();
  5988. return widgetControl;
  5989. }
  5990. };
  5991. module.exports = mediaWidget;
  5992. },{}],33:[function(require,module,exports){
  5993. var $ = jQuery;
  5994. var textWidget = {
  5995. addWidget: function( idBase, widgetContainer, widgetId ) {
  5996. var component = wp.textWidgets;
  5997. var options = {};
  5998. var visualField = widgetContainer.find( '.visual' );
  5999. // 'visual' field and syncContainer were introduced together in 4.8.1
  6000. if ( visualField.length > 0 ) {
  6001. // If 'visual' field has no value it's a legacy text widget.
  6002. if ( ! visualField.val() ) {
  6003. return null;
  6004. }
  6005. var fieldContainer = $( '<div></div>' );
  6006. var syncContainer = widgetContainer.find( '.widget-content:first' );
  6007. syncContainer.before( fieldContainer );
  6008. options = {
  6009. el: fieldContainer,
  6010. syncContainer: syncContainer,
  6011. };
  6012. } else {
  6013. options = { el: widgetContainer };
  6014. }
  6015. var widgetControl = new component.TextWidgetControl( options );
  6016. widgetControl.initializeEditor();
  6017. return widgetControl;
  6018. }
  6019. };
  6020. module.exports = textWidget;
  6021. },{}]},{},[16]);