PageRenderTime 73ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/siteorigin-panels/js/siteorigin-panels-24.js

https://gitlab.com/hop23typhu/faci-parkhill
JavaScript | 1863 lines | 1251 code | 332 blank | 280 comment | 144 complexity | 3c6a997757426f8ffa82813965969692 MD5 | raw file
  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. visualSortComparator: function ( item ) {
  19. if ( ! _.isNull( item.indexes ) ) {
  20. return item.indexes.builder;
  21. } else {
  22. return null;
  23. }
  24. },
  25. visualSort: function(){
  26. var oldComparator = this.comparator;
  27. this.comparator = this.visualSortComparator;
  28. this.sort();
  29. this.comparator = oldComparator;
  30. }
  31. } );
  32. },{}],2:[function(require,module,exports){
  33. var panels = window.panels;
  34. module.exports = Backbone.Collection.extend( {
  35. model: panels.model.historyEntry,
  36. /**
  37. * The builder model
  38. */
  39. builder: null,
  40. /**
  41. * The maximum number of items in the history
  42. */
  43. maxSize: 12,
  44. initialize: function () {
  45. this.on( 'add', this.onAddEntry, this );
  46. },
  47. /**
  48. * Add an entry to the collection.
  49. *
  50. * @param text The text that defines the action taken to get to this
  51. * @param data
  52. */
  53. addEntry: function ( text, data ) {
  54. if ( _.isEmpty( data ) ) {
  55. data = this.builder.getPanelsData();
  56. }
  57. var entry = new panels.model.historyEntry( {
  58. text: text,
  59. data: JSON.stringify( data ),
  60. time: parseInt( new Date().getTime() / 1000 ),
  61. collection: this
  62. } );
  63. this.add( entry );
  64. },
  65. /**
  66. * Resize the collection so it's not bigger than this.maxSize
  67. */
  68. onAddEntry: function ( entry ) {
  69. if ( this.models.length > 1 ) {
  70. var lastEntry = this.at( this.models.length - 2 );
  71. if (
  72. (
  73. entry.get( 'text' ) === lastEntry.get( 'text' ) && entry.get( 'time' ) - lastEntry.get( 'time' ) < 15
  74. ) ||
  75. (
  76. entry.get( 'data' ) === lastEntry.get( 'data' )
  77. )
  78. ) {
  79. // If both entries have the same text and are within 20 seconds of each other, or have the same data, then remove most recent
  80. this.remove( entry );
  81. lastEntry.set( 'count', lastEntry.get( 'count' ) + 1 );
  82. }
  83. }
  84. // Make sure that there are not to many entries in this collection
  85. while ( this.models.length > this.maxSize ) {
  86. this.shift();
  87. }
  88. }
  89. } );
  90. },{}],3:[function(require,module,exports){
  91. var panels = window.panels;
  92. module.exports = Backbone.Collection.extend( {
  93. model: panels.model.row,
  94. /**
  95. * Destroy all the rows in this collection
  96. */
  97. empty: function () {
  98. var model;
  99. do {
  100. model = this.collection.first();
  101. if ( ! model ) {
  102. break;
  103. }
  104. model.destroy();
  105. } while ( true );
  106. },
  107. visualSortComparator: function ( item ) {
  108. if ( ! _.isNull( item.indexes ) ) {
  109. return item.indexes.builder;
  110. } else {
  111. return null;
  112. }
  113. },
  114. visualSort: function(){
  115. var oldComparator = this.comparator;
  116. this.comparator = this.visualSortComparator;
  117. this.sort();
  118. this.comparator = oldComparator;
  119. }
  120. } );
  121. },{}],4:[function(require,module,exports){
  122. var panels = window.panels;
  123. module.exports = Backbone.Collection.extend( {
  124. model: panels.model.widget,
  125. initialize: function () {
  126. },
  127. visualSortComparator: function ( item ) {
  128. if ( ! _.isNull( item.indexes ) ) {
  129. return item.indexes.builder;
  130. } else {
  131. return null;
  132. }
  133. },
  134. visualSort: function(){
  135. var oldComparator = this.comparator;
  136. this.comparator = this.visualSortComparator;
  137. this.sort();
  138. this.comparator = oldComparator;
  139. }
  140. } );
  141. },{}],5:[function(require,module,exports){
  142. var panels = window.panels, $ = jQuery;
  143. module.exports = panels.view.dialog.extend( {
  144. dialogClass: 'so-panels-dialog-add-builder',
  145. render: function () {
  146. // Render the dialog and attach it to the builder interface
  147. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-builder' ).html(), {} ) );
  148. this.$( '.so-content .siteorigin-panels-builder' ).append( this.builder.$el );
  149. },
  150. initializeDialog: function () {
  151. var thisView = this;
  152. this.once( 'open_dialog_complete', function () {
  153. thisView.builder.initSortable();
  154. } );
  155. this.on( 'open_dialog_complete', function () {
  156. thisView.builder.trigger( 'builder_resize' );
  157. } );
  158. }
  159. } );
  160. },{}],6:[function(require,module,exports){
  161. var panels = window.panels, $ = jQuery;
  162. module.exports = panels.view.dialog.extend( {
  163. historyEntryTemplate: _.template( $( '#siteorigin-panels-dialog-history-entry' ).html().panelsProcessTemplate() ),
  164. entries: {},
  165. currentEntry: null,
  166. revertEntry: null,
  167. selectedEntry: null,
  168. previewScrollTop: null,
  169. dialogClass: 'so-panels-dialog-history',
  170. events: {
  171. 'click .so-close': 'closeDialog',
  172. 'click .so-restore': 'restoreSelectedEntry'
  173. },
  174. initializeDialog: function () {
  175. this.entries = new panels.collection.historyEntries();
  176. this.on( 'open_dialog', this.setCurrentEntry, this );
  177. this.on( 'open_dialog', this.renderHistoryEntries, this );
  178. },
  179. render: function () {
  180. var thisView = this;
  181. // Render the dialog and attach it to the builder interface
  182. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-history' ).html(), {} ) );
  183. this.$( 'iframe.siteorigin-panels-history-iframe' ).load( function () {
  184. var $$ = $( this );
  185. $$.show();
  186. $$.contents().scrollTop( thisView.previewScrollTop );
  187. } );
  188. },
  189. /**
  190. * Set the original entry. This should be set when creating the dialog.
  191. *
  192. * @param {panels.model.builder} builder
  193. */
  194. setRevertEntry: function ( builder ) {
  195. this.revertEntry = new panels.model.historyEntry( {
  196. data: JSON.stringify( builder.getPanelsData() ),
  197. time: parseInt( new Date().getTime() / 1000 )
  198. } );
  199. },
  200. /**
  201. * This is triggered when the dialog is opened.
  202. */
  203. setCurrentEntry: function () {
  204. this.currentEntry = new panels.model.historyEntry( {
  205. data: JSON.stringify( this.builder.model.getPanelsData() ),
  206. time: parseInt( new Date().getTime() / 1000 )
  207. } );
  208. this.selectedEntry = this.currentEntry;
  209. this.previewEntry( this.currentEntry );
  210. this.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
  211. },
  212. /**
  213. * Render the history entries in the sidebar
  214. */
  215. renderHistoryEntries: function () {
  216. // Set up an interval that will display the time since every 10 seconds
  217. var thisView = this;
  218. var c = this.$( '.history-entries' ).empty();
  219. if ( this.currentEntry.get( 'data' ) !== this.revertEntry.get( 'data' ) || ! _.isEmpty( this.entries.models ) ) {
  220. $( this.historyEntryTemplate( {title: panelsOptions.loc.history.revert, count: 1} ) )
  221. .data( 'historyEntry', this.revertEntry )
  222. .prependTo( c );
  223. }
  224. // Now load all the entries in this.entries
  225. this.entries.each( function ( entry ) {
  226. var html = thisView.historyEntryTemplate( {
  227. title: panelsOptions.loc.history[entry.get( 'text' )],
  228. count: entry.get( 'count' )
  229. } );
  230. $( html )
  231. .data( 'historyEntry', entry )
  232. .prependTo( c );
  233. } );
  234. $( this.historyEntryTemplate( {title: panelsOptions.loc.history['current'], count: 1} ) )
  235. .data( 'historyEntry', this.currentEntry )
  236. .addClass( 'so-selected' )
  237. .prependTo( c );
  238. // Handle loading and selecting
  239. c.find( '.history-entry' ).click( function () {
  240. var $$ = jQuery( this );
  241. c.find( '.history-entry' ).not( $$ ).removeClass( 'so-selected' );
  242. $$.addClass( 'so-selected' );
  243. var entry = $$.data( 'historyEntry' );
  244. thisView.selectedEntry = entry;
  245. if ( thisView.selectedEntry.cid !== thisView.currentEntry.cid ) {
  246. thisView.$( '.so-buttons .so-restore' ).removeClass( 'disabled' );
  247. } else {
  248. thisView.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
  249. }
  250. thisView.previewEntry( entry );
  251. } );
  252. this.updateEntryTimes();
  253. },
  254. /**
  255. * Preview an entry
  256. *
  257. * @param entry
  258. */
  259. previewEntry: function ( entry ) {
  260. var iframe = this.$( 'iframe.siteorigin-panels-history-iframe' );
  261. iframe.hide();
  262. this.previewScrollTop = iframe.contents().scrollTop();
  263. this.$( 'form.history-form input[name="live_editor_panels_data"]' ).val( entry.get( 'data' ) );
  264. this.$( 'form.history-form' ).submit();
  265. },
  266. /**
  267. * Restore the current entry
  268. */
  269. restoreSelectedEntry: function () {
  270. if ( this.$( '.so-buttons .so-restore' ).hasClass( 'disabled' ) ) {
  271. return false;
  272. }
  273. if ( this.currentEntry.get( 'data' ) === this.selectedEntry.get( 'data' ) ) {
  274. this.closeDialog();
  275. return false;
  276. }
  277. // Add an entry for this restore event
  278. if ( this.selectedEntry.get( 'text' ) !== 'restore' ) {
  279. this.builder.addHistoryEntry( 'restore', this.builder.model.getPanelsData() );
  280. }
  281. this.builder.model.loadPanelsData( JSON.parse( this.selectedEntry.get( 'data' ) ) );
  282. this.closeDialog();
  283. return false;
  284. },
  285. /**
  286. * Update the entry times for the list of entries down the side
  287. */
  288. updateEntryTimes: function () {
  289. var thisView = this;
  290. this.$( '.history-entries .history-entry' ).each( function () {
  291. var $$ = jQuery( this );
  292. var time = $$.find( '.timesince' );
  293. var entry = $$.data( 'historyEntry' );
  294. time.html( thisView.timeSince( entry.get( 'time' ) ) );
  295. } );
  296. },
  297. /**
  298. * Gets the time since as a nice string.
  299. *
  300. * @param date
  301. */
  302. timeSince: function ( time ) {
  303. var diff = parseInt( new Date().getTime() / 1000 ) - time;
  304. var parts = [];
  305. var interval;
  306. // There are 3600 seconds in an hour
  307. if ( diff > 3600 ) {
  308. interval = Math.floor( diff / 3600 );
  309. if ( interval === 1 ) {
  310. parts.push( panelsOptions.loc.time.hour.replace( '%d', interval ) );
  311. } else {
  312. parts.push( panelsOptions.loc.time.hours.replace( '%d', interval ) );
  313. }
  314. diff -= interval * 3600;
  315. }
  316. // There are 60 seconds in a minute
  317. if ( diff > 60 ) {
  318. interval = Math.floor( diff / 60 );
  319. if ( interval === 1 ) {
  320. parts.push( panelsOptions.loc.time.minute.replace( '%d', interval ) );
  321. } else {
  322. parts.push( panelsOptions.loc.time.minutes.replace( '%d', interval ) );
  323. }
  324. diff -= interval * 60;
  325. }
  326. if ( diff > 0 ) {
  327. if ( diff === 1 ) {
  328. parts.push( panelsOptions.loc.time.second.replace( '%d', diff ) );
  329. } else {
  330. parts.push( panelsOptions.loc.time.seconds.replace( '%d', diff ) );
  331. }
  332. }
  333. // Return the amount of time ago
  334. return _.isEmpty( parts ) ? panelsOptions.loc.time.now : panelsOptions.loc.time.ago.replace( '%s', parts.slice( 0, 2 ).join( ', ' ) );
  335. }
  336. } );
  337. },{}],7:[function(require,module,exports){
  338. var panels = window.panels, $ = jQuery;
  339. module.exports = panels.view.dialog.extend( {
  340. directoryTemplate: _.template( $( '#siteorigin-panels-directory-items' ).html().panelsProcessTemplate() ),
  341. builder: null,
  342. dialogClass: 'so-panels-dialog-prebuilt-layouts',
  343. layoutCache: {},
  344. currentTab: false,
  345. directoryPage: 1,
  346. events: {
  347. 'click .so-close': 'closeDialog',
  348. 'click .so-sidebar-tabs li a': 'tabClickHandler',
  349. 'click .so-content .layout': 'layoutClickHandler',
  350. 'keyup .so-sidebar-search': 'searchHandler',
  351. // The directory items
  352. 'click .so-screenshot, .so-title': 'directoryItemClickHandler'
  353. },
  354. /**
  355. * Initialize the prebuilt dialog.
  356. */
  357. initializeDialog: function () {
  358. var thisView = this;
  359. this.on( 'open_dialog', function () {
  360. thisView.$( '.so-sidebar-tabs li a' ).first().click();
  361. thisView.$( '.so-status' ).removeClass( 'so-panels-loading' );
  362. } );
  363. this.on( 'button_click', this.toolbarButtonClick, this );
  364. },
  365. /**
  366. * Render the prebuilt layouts dialog
  367. */
  368. render: function () {
  369. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-prebuilt' ).html(), {} ) );
  370. this.initToolbar();
  371. },
  372. /**
  373. *
  374. * @param e
  375. * @return {boolean}
  376. */
  377. tabClickHandler: function ( e ) {
  378. e.preventDefault();
  379. // Reset selected item state when changing tabs
  380. this.selectedLayoutItem = null;
  381. this.uploadedLayout = null;
  382. this.updateButtonState( false );
  383. this.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
  384. var $$ = $( e.target );
  385. var tab = $$.attr( 'href' ).split( '#' )[1];
  386. $$.parent().addClass( 'tab-active' );
  387. var thisView = this;
  388. // Empty everything
  389. this.$( '.so-content' ).empty();
  390. thisView.currentTab = tab;
  391. if ( tab == 'import' ) {
  392. this.displayImportExport();
  393. } else {
  394. this.displayLayoutDirectory( '', 1, tab );
  395. }
  396. thisView.$( '.so-sidebar-search' ).val( '' );
  397. },
  398. /**
  399. * Display and setup the import/export form
  400. */
  401. displayImportExport: function () {
  402. var c = this.$( '.so-content' ).empty().removeClass( 'so-panels-loading' );
  403. c.html( $( '#siteorigin-panels-dialog-prebuilt-importexport' ).html() );
  404. var thisView = this;
  405. var uploadUi = thisView.$( '.import-upload-ui' ).hide();
  406. // Create the uploader
  407. var uploader = new plupload.Uploader( {
  408. runtimes: 'html5,silverlight,flash,html4',
  409. browse_button: uploadUi.find( '.file-browse-button' ).get( 0 ),
  410. container: uploadUi.get( 0 ),
  411. drop_element: uploadUi.find( '.drag-upload-area' ).get( 0 ),
  412. file_data_name: 'panels_import_data',
  413. multiple_queues: false,
  414. max_file_size: panelsOptions.plupload.max_file_size,
  415. url: panelsOptions.plupload.url,
  416. flash_swf_url: panelsOptions.plupload.flash_swf_url,
  417. silverlight_xap_url: panelsOptions.plupload.silverlight_xap_url,
  418. filters: [
  419. {title: panelsOptions.plupload.filter_title, extensions: 'json'}
  420. ],
  421. multipart_params: {
  422. action: 'so_panels_import_layout'
  423. },
  424. init: {
  425. PostInit: function ( uploader ) {
  426. if ( uploader.features.dragdrop ) {
  427. uploadUi.addClass( 'has-drag-drop' );
  428. }
  429. uploadUi.show().find( '.progress-precent' ).css( 'width', '0%' );
  430. },
  431. FilesAdded: function ( uploader ) {
  432. uploadUi.find( '.file-browse-button' ).blur();
  433. uploadUi.find( '.drag-upload-area' ).removeClass( 'file-dragover' );
  434. uploadUi.find( '.progress-bar' ).fadeIn( 'fast' );
  435. thisView.$( '.js-so-selected-file' ).text( panelsOptions.loc.prebuilt_loading );
  436. uploader.start();
  437. },
  438. UploadProgress: function ( uploader, file ) {
  439. uploadUi.find( '.progress-precent' ).css( 'width', file.percent + '%' );
  440. },
  441. FileUploaded: function ( uploader, file, response ) {
  442. var layout = JSON.parse( response.response );
  443. if ( ! _.isUndefined( layout.widgets ) ) {
  444. thisView.uploadedLayout = layout;
  445. uploadUi.find( '.progress-bar' ).hide();
  446. thisView.$( '.js-so-selected-file' ).text(
  447. panelsOptions.loc.ready_to_insert.replace( '%s', file.name )
  448. );
  449. thisView.updateButtonState( true );
  450. } else {
  451. alert( panelsOptions.plupload.error_message );
  452. }
  453. },
  454. Error: function () {
  455. alert( panelsOptions.plupload.error_message );
  456. }
  457. }
  458. } );
  459. uploader.init();
  460. // This is
  461. uploadUi.find( '.drag-upload-area' )
  462. .on( 'dragover', function () {
  463. $( this ).addClass( 'file-dragover' );
  464. } )
  465. .on( 'dragleave', function () {
  466. $( this ).removeClass( 'file-dragover' );
  467. } );
  468. // Handle exporting the file
  469. c.find( '.so-export' ).submit( function ( e ) {
  470. var $$ = jQuery( this );
  471. $$.find( 'input[name="panels_export_data"]' ).val( JSON.stringify( thisView.builder.model.getPanelsData() ) );
  472. } );
  473. },
  474. /**
  475. * Display the layout directory tab.
  476. *
  477. * @param query
  478. */
  479. displayLayoutDirectory: function ( search, page, type ) {
  480. var thisView = this;
  481. var c = this.$( '.so-content' ).empty().addClass( 'so-panels-loading' );
  482. if ( search === undefined ) {
  483. search = '';
  484. }
  485. if ( page === undefined ) {
  486. page = 1;
  487. }
  488. if ( type === undefined ) {
  489. type = 'directory';
  490. }
  491. if ( type === 'directory' && ! panelsOptions.directory_enabled ) {
  492. // Display the button to enable the prebuilt layout
  493. c.removeClass( 'so-panels-loading' ).html( $( '#siteorigin-panels-directory-enable' ).html() );
  494. c.find( '.so-panels-enable-directory' ).click( function ( e ) {
  495. e.preventDefault();
  496. // Sent the query to enable the directory, then enable the directory
  497. $.get(
  498. panelsOptions.ajaxurl,
  499. {action: 'so_panels_directory_enable'},
  500. function () {
  501. }
  502. );
  503. // Enable the layout directory
  504. panelsOptions.directory_enabled = true;
  505. c.addClass( 'so-panels-loading' );
  506. thisView.displayLayoutDirectory( search, page );
  507. } );
  508. return;
  509. }
  510. // Get all the items for the current query
  511. $.get(
  512. panelsOptions.ajaxurl,
  513. {
  514. action: 'so_panels_layouts_query',
  515. search: search,
  516. page: page,
  517. type: type,
  518. },
  519. function ( data ) {
  520. // Skip this if we're no longer viewing the layout directory
  521. if ( thisView.currentTab !== type ) {
  522. return;
  523. }
  524. // Add the directory items
  525. c.removeClass( 'so-panels-loading' ).html( thisView.directoryTemplate( data ) );
  526. // Lets setup the next and previous buttons
  527. var prev = c.find( '.so-previous' ), next = c.find( '.so-next' );
  528. if ( page <= 1 ) {
  529. prev.addClass( 'button-disabled' );
  530. } else {
  531. prev.click( function ( e ) {
  532. e.preventDefault();
  533. thisView.displayLayoutDirectory( search, page - 1, thisView.currentTab );
  534. } );
  535. }
  536. if ( page === data.max_num_pages || data.max_num_pages === 0 ) {
  537. next.addClass( 'button-disabled' );
  538. } else {
  539. next.click( function ( e ) {
  540. e.preventDefault();
  541. thisView.displayLayoutDirectory( search, page + 1, thisView.currentTab );
  542. } );
  543. }
  544. // Handle nice preloading of the screenshots
  545. c.find( '.so-screenshot' ).each( function () {
  546. var $$ = $( this ), $a = $$.find( '.so-screenshot-wrapper' );
  547. $a.css( 'height', (
  548. $a.width() / 4 * 3
  549. ) + 'px' ).addClass( 'so-loading' );
  550. if ( $$.data( 'src' ) !== '' ) {
  551. // Set the initial height
  552. var $img = $( '<img/>' ).attr( 'src', $$.data( 'src' ) ).load( function () {
  553. $a.removeClass( 'so-loading' ).css( 'height', 'auto' );
  554. $img.appendTo( $a ).hide().fadeIn( 'fast' );
  555. } );
  556. } else {
  557. $( '<img/>' ).attr( 'src', panelsOptions.prebuiltDefaultScreenshot ).appendTo( $a ).hide().fadeIn( 'fast' );
  558. }
  559. } );
  560. // Set the title
  561. c.find( '.so-directory-browse' ).html( data.title );
  562. },
  563. 'json'
  564. );
  565. },
  566. /**
  567. * Set the selected state for the clicked layout directory item and remove previously selected item.
  568. * Enable the toolbar buttons.
  569. */
  570. directoryItemClickHandler: function ( e ) {
  571. var $directoryItem = this.$( e.target ).closest( '.so-directory-item' );
  572. this.$( '.so-directory-items' ).find( '.selected' ).removeClass( 'selected' );
  573. $directoryItem.addClass( 'selected' );
  574. this.selectedLayoutItem = {lid: $directoryItem.data( 'layout-id' ), type: $directoryItem.data( 'layout-type' )};
  575. this.updateButtonState( true );
  576. },
  577. /**
  578. * Load a particular layout into the builder.
  579. *
  580. * @param id
  581. */
  582. toolbarButtonClick: function ( $button ) {
  583. if ( ! this.canAddLayout() ) {
  584. return false;
  585. }
  586. var position = $button.data( 'value' );
  587. if ( _.isUndefined( position ) ) {
  588. return false;
  589. }
  590. this.updateButtonState( false );
  591. if ( $button.hasClass( 'so-needs-confirm' ) && ! $button.hasClass( 'so-confirmed' ) ) {
  592. this.updateButtonState( true );
  593. if ( $button.hasClass( 'so-confirming' ) ) {
  594. return;
  595. }
  596. $button.addClass( 'so-confirming' );
  597. var originalText = $button.html();
  598. $button.html( '<span class="dashicons dashicons-yes"></span>' + $button.data( 'confirm' ) );
  599. setTimeout( function () {
  600. $button.removeClass( 'so-confirmed' ).html( originalText );
  601. }, 2500 );
  602. setTimeout( function () {
  603. $button.removeClass( 'so-confirming' );
  604. $button.addClass( 'so-confirmed' );
  605. }, 200 );
  606. return false;
  607. }
  608. this.addingLayout = true;
  609. if ( this.currentTab === 'import' ) {
  610. this.addLayoutToBuilder( this.uploadedLayout, position );
  611. } else {
  612. this.loadSelectedLayout().then( function ( layout ) {
  613. this.addLayoutToBuilder( layout, position );
  614. }.bind( this ) );
  615. }
  616. },
  617. canAddLayout: function () {
  618. return (
  619. this.selectedLayoutItem || this.uploadedLayout
  620. ) && ! this.addingLayout;
  621. },
  622. /**
  623. * Load the layout according to selectedLayoutItem.
  624. */
  625. loadSelectedLayout: function () {
  626. this.setStatusMessage( panelsOptions.loc.prebuilt_loading, true );
  627. var args = _.extend( this.selectedLayoutItem, {action: 'so_panels_get_layout'} );
  628. var deferredLayout = new $.Deferred();
  629. $.get(
  630. panelsOptions.ajaxurl,
  631. args,
  632. function ( layout ) {
  633. if ( layout.error !== undefined ) {
  634. // There was an error
  635. alert( layout.error );
  636. deferredLayout.reject( layout );
  637. } else {
  638. this.setStatusMessage( '', false );
  639. deferredLayout.resolve( layout );
  640. }
  641. }.bind( this )
  642. );
  643. return deferredLayout.promise();
  644. },
  645. /**
  646. * Handle an update to the search
  647. */
  648. searchHandler: function ( e ) {
  649. if ( e.keyCode === 13 ) {
  650. this.displayLayoutDirectory( $( e.currentTarget ).val(), 1, this.currentTab );
  651. }
  652. },
  653. /**
  654. * Attempt to set the 'Insert' button's state according to the `enabled` argument, also checking whether the
  655. * requirements for inserting a layout have valid values.
  656. */
  657. updateButtonState: function ( enabled ) {
  658. enabled = enabled && (
  659. this.selectedLayoutItem || this.uploadedLayout
  660. );
  661. var $button = this.$( '.so-import-layout' );
  662. $button.prop( "disabled", ! enabled );
  663. if ( enabled ) {
  664. $button.removeClass( 'disabled' );
  665. } else {
  666. $button.addClass( 'disabled' );
  667. }
  668. },
  669. addLayoutToBuilder: function ( layout, position ) {
  670. this.builder.addHistoryEntry( 'prebuilt_loaded' );
  671. this.builder.model.loadPanelsData( layout, position );
  672. this.addingLayout = false;
  673. this.closeDialog();
  674. }
  675. } );
  676. },{}],8:[function(require,module,exports){
  677. var panels = window.panels, $ = jQuery;
  678. module.exports = panels.view.dialog.extend( {
  679. cellPreviewTemplate: _.template( $( '#siteorigin-panels-dialog-row-cell-preview' ).html().panelsProcessTemplate() ),
  680. events: {
  681. 'click .so-close': 'closeDialog',
  682. // Toolbar buttons
  683. 'click .so-toolbar .so-save': 'saveHandler',
  684. 'click .so-toolbar .so-insert': 'insertHandler',
  685. 'click .so-toolbar .so-delete': 'deleteHandler',
  686. 'click .so-toolbar .so-duplicate': 'duplicateHandler',
  687. // Changing the row
  688. 'change .row-set-form > *': 'setCellsFromForm',
  689. 'click .row-set-form button.set-row': 'setCellsFromForm'
  690. },
  691. dialogClass: 'so-panels-dialog-row-edit',
  692. styleType: 'row',
  693. dialogType: 'edit',
  694. /**
  695. * The current settings, not yet saved to the model
  696. */
  697. row: {
  698. // This is just the cell weights, cell content is not edited by this dialog
  699. cells: [],
  700. // The style settings of the row
  701. style: {}
  702. },
  703. initializeDialog: function () {
  704. this.on( 'open_dialog', function () {
  705. if ( ! _.isUndefined( this.model ) && ! _.isEmpty( this.model.cells ) ) {
  706. this.setRowModel( this.model );
  707. } else {
  708. this.setRowModel( null );
  709. }
  710. this.regenerateRowPreview();
  711. }, this );
  712. // This is the default row layout
  713. this.row = {
  714. cells: [0.5, 0.5],
  715. style: {}
  716. };
  717. // Refresh panels data after both dialog form components are loaded
  718. this.dialogFormsLoaded = 0;
  719. var thisView = this;
  720. this.on( 'form_loaded styles_loaded', function () {
  721. this.dialogFormsLoaded ++;
  722. if ( this.dialogFormsLoaded === 2 ) {
  723. thisView.updateModel( {
  724. refreshArgs: {
  725. silent: true
  726. }
  727. } );
  728. }
  729. } );
  730. },
  731. /**
  732. *
  733. * @param dialogType Either "edit" or "create"
  734. */
  735. setRowDialogType: function ( dialogType ) {
  736. this.dialogType = dialogType;
  737. },
  738. /**
  739. * Render the new row dialog
  740. */
  741. render: function ( dialogType ) {
  742. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-row' ).html(), {dialogType: this.dialogType} ) );
  743. if ( this.dialogType === 'edit' ) {
  744. // Now we need to attach the style window
  745. this.styles = new panels.view.styles();
  746. this.styles.model = this.model;
  747. this.styles.render( 'row', $( '#post_ID' ).val(), {
  748. builderType: this.builder.builderType,
  749. dialog: this
  750. } );
  751. var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
  752. this.styles.attach( $rightSidebar );
  753. // Handle the loading class
  754. this.styles.on( 'styles_loaded', function ( hasStyles ) {
  755. // If we have styles remove the loading spinner, else remove the whole empty sidebar.
  756. if ( hasStyles ) {
  757. $rightSidebar.removeClass( 'so-panels-loading' );
  758. } else {
  759. $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  760. $rightSidebar.remove();
  761. }
  762. }, this );
  763. $rightSidebar.addClass( 'so-panels-loading' );
  764. }
  765. if ( ! _.isUndefined( this.model ) ) {
  766. // Set the initial value of the
  767. this.$( 'input.so-row-field' ).val( this.model.cells.length );
  768. }
  769. var thisView = this;
  770. this.$( 'input.so-row-field' ).keyup( function () {
  771. $( this ).trigger( 'change' );
  772. } );
  773. return this;
  774. },
  775. /**
  776. * Set the row model we'll be using for this dialog.
  777. *
  778. * @param model
  779. */
  780. setRowModel: function ( model ) {
  781. this.model = model;
  782. if ( _.isEmpty( this.model ) ) {
  783. return this;
  784. }
  785. // Set the rows to be a copy of the model
  786. this.row = {
  787. cells: this.model.cells.map( function ( cell ) {
  788. return cell.get( 'weight' );
  789. } ),
  790. style: {}
  791. };
  792. // Set the initial value of the cell field.
  793. this.$( 'input.so-row-field' ).val( this.model.cells.length );
  794. return this;
  795. },
  796. /**
  797. * Regenerate the row preview and resizing interface.
  798. */
  799. regenerateRowPreview: function () {
  800. var thisDialog = this;
  801. var rowPreview = this.$( '.row-preview' );
  802. rowPreview.empty();
  803. var timeout;
  804. // Represent the cells
  805. _.each( this.row.cells, function ( cell, i ) {
  806. var newCell = $( this.cellPreviewTemplate( {weight: cell} ) );
  807. rowPreview.append( newCell );
  808. var prevCell = newCell.prev();
  809. var handle;
  810. if ( prevCell.length ) {
  811. handle = $( '<div class="resize-handle"></div>' );
  812. handle
  813. .appendTo( newCell )
  814. .dblclick( function () {
  815. var t = thisDialog.row.cells[i] + thisDialog.row.cells[i - 1];
  816. thisDialog.row.cells[i] = thisDialog.row.cells[i - 1] = t / 2;
  817. thisDialog.scaleRowWidths();
  818. } );
  819. handle.draggable( {
  820. axis: 'x',
  821. containment: rowPreview,
  822. start: function ( e, ui ) {
  823. // Create the clone for the current cell
  824. var newCellClone = newCell.clone().appendTo( ui.helper ).css( {
  825. position: 'absolute',
  826. top: '0',
  827. width: newCell.outerWidth(),
  828. left: 6,
  829. height: newCell.outerHeight()
  830. } );
  831. newCellClone.find( '.resize-handle' ).remove();
  832. // Create the clone for the previous cell
  833. var prevCellClone = prevCell.clone().appendTo( ui.helper ).css( {
  834. position: 'absolute',
  835. top: '0',
  836. width: prevCell.outerWidth(),
  837. right: 6,
  838. height: prevCell.outerHeight()
  839. } );
  840. prevCellClone.find( '.resize-handle' ).remove();
  841. $( this ).data( {
  842. 'newCellClone': newCellClone,
  843. 'prevCellClone': prevCellClone
  844. } );
  845. // Hide the
  846. newCell.find( '> .preview-cell-in' ).css( 'visibility', 'hidden' );
  847. prevCell.find( '> .preview-cell-in' ).css( 'visibility', 'hidden' );
  848. },
  849. drag: function ( e, ui ) {
  850. // Calculate the new cell and previous cell widths as a percent
  851. var ncw = thisDialog.row.cells[i] - (
  852. (
  853. ui.position.left + 6
  854. ) / rowPreview.width()
  855. );
  856. var pcw = thisDialog.row.cells[i - 1] + (
  857. (
  858. ui.position.left + 6
  859. ) / rowPreview.width()
  860. );
  861. var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6;
  862. $( this ).data( 'newCellClone' ).css( 'width', rowPreview.width() * ncw )
  863. .find( '.preview-cell-weight' ).html( Math.round( ncw * 1000 ) / 10 );
  864. $( this ).data( 'prevCellClone' ).css( 'width', rowPreview.width() * pcw )
  865. .find( '.preview-cell-weight' ).html( Math.round( pcw * 1000 ) / 10 );
  866. },
  867. stop: function ( e, ui ) {
  868. // Remove the clones
  869. $( this ).data( 'newCellClone' ).remove();
  870. $( this ).data( 'prevCellClone' ).remove();
  871. // Reshow the main cells
  872. newCell.find( '.preview-cell-in' ).css( 'visibility', 'visible' );
  873. prevCell.find( '.preview-cell-in' ).css( 'visibility', 'visible' );
  874. // Calculate the new cell weights
  875. var offset = ui.position.left + 6;
  876. var percent = offset / rowPreview.width();
  877. // Ignore this if any of the cells are below 2% in width.
  878. if ( thisDialog.row.cells[i] - percent > 0.02 && thisDialog.row.cells[i - 1] + percent > 0.02 ) {
  879. thisDialog.row.cells[i] -= percent;
  880. thisDialog.row.cells[i - 1] += percent;
  881. }
  882. thisDialog.scaleRowWidths();
  883. ui.helper.css( 'left', - 6 );
  884. }
  885. } );
  886. }
  887. // Make this row weight click editable
  888. newCell.find( '.preview-cell-weight' ).click( function ( ci ) {
  889. // Disable the draggable while entering values
  890. thisDialog.$( '.resize-handle' ).css( 'pointer-event', 'none' ).draggable( 'disable' );
  891. rowPreview.find( '.preview-cell-weight' ).each( function () {
  892. var $$ = jQuery( this ).hide();
  893. $( '<input type="text" class="preview-cell-weight-input no-user-interacted" />' )
  894. .val( parseFloat( $$.html() ) ).insertAfter( $$ )
  895. .focus( function () {
  896. clearTimeout( timeout );
  897. } )
  898. .keyup( function ( e ) {
  899. if ( e.keyCode !== 9 ) {
  900. // Only register the interaction if the user didn't press tab
  901. $( this ).removeClass( 'no-user-interacted' );
  902. }
  903. // Enter is clicked
  904. if ( e.keyCode === 13 ) {
  905. e.preventDefault();
  906. $( this ).blur();
  907. }
  908. } )
  909. .keydown( function ( e ) {
  910. if ( e.keyCode === 9 ) {
  911. e.preventDefault();
  912. // Tab will always cycle around the row inputs
  913. var inputs = rowPreview.find( '.preview-cell-weight-input' );
  914. var i = inputs.index( $( this ) );
  915. if ( i === inputs.length - 1 ) {
  916. inputs.eq( 0 ).focus().select();
  917. } else {
  918. inputs.eq( i + 1 ).focus().select();
  919. }
  920. }
  921. } )
  922. .blur( function () {
  923. rowPreview.find( '.preview-cell-weight-input' ).each( function ( i, el ) {
  924. if ( isNaN( parseFloat( $( el ).val() ) ) ) {
  925. $( el ).val( Math.floor( thisDialog.row.cells[i] * 1000 ) / 10 );
  926. }
  927. } );
  928. timeout = setTimeout( function () {
  929. // If there are no weight inputs, then skip this
  930. if ( rowPreview.find( '.preview-cell-weight-input' ).legnth === 0 ) {
  931. return false;
  932. }
  933. // Go through all the inputs
  934. var rowWeights = [],
  935. rowChanged = [],
  936. changedSum = 0,
  937. unchangedSum = 0;
  938. rowPreview.find( '.preview-cell-weight-input' ).each( function ( i, el ) {
  939. var val = parseFloat( $( el ).val() );
  940. if ( isNaN( val ) ) {
  941. val = 1 / thisDialog.row.cells.length;
  942. } else {
  943. val = Math.round( val * 10 ) / 1000;
  944. }
  945. // Check within 3 decimal points
  946. var changed = ! $( el ).hasClass( 'no-user-interacted' );
  947. rowWeights.push( val );
  948. rowChanged.push( changed );
  949. if ( changed ) {
  950. changedSum += val;
  951. } else {
  952. unchangedSum += val;
  953. }
  954. } );
  955. if ( changedSum > 0 && unchangedSum > 0 && (
  956. 1 - changedSum
  957. ) > 0 ) {
  958. // Balance out the unchanged rows to occupy the weight left over by the changed sum
  959. for ( var i = 0; i < rowWeights.length; i ++ ) {
  960. if ( ! rowChanged[i] ) {
  961. rowWeights[i] = (
  962. rowWeights[i] / unchangedSum
  963. ) * (
  964. 1 - changedSum
  965. );
  966. }
  967. }
  968. }
  969. // Last check to ensure total weight is 1
  970. var sum = _.reduce( rowWeights, function ( memo, num ) {
  971. return memo + num;
  972. } );
  973. rowWeights = rowWeights.map( function ( w ) {
  974. return w / sum;
  975. } );
  976. // Set the new cell weights and regenerate the preview.
  977. if ( Math.min.apply( Math, rowWeights ) > 0.01 ) {
  978. thisDialog.row.cells = rowWeights;
  979. }
  980. // Now lets animate the cells into their new widths
  981. rowPreview.find( '.preview-cell' ).each( function ( i, el ) {
  982. $( el ).animate( {'width': Math.round( thisDialog.row.cells[i] * 1000 ) / 10 + "%"}, 250 );
  983. $( el ).find( '.preview-cell-weight-input' ).val( Math.round( thisDialog.row.cells[i] * 1000 ) / 10 );
  984. } );
  985. // So the draggable handle is not hidden.
  986. rowPreview.find( '.preview-cell' ).css( 'overflow', 'visible' );
  987. setTimeout( function () {
  988. thisDialog.regenerateRowPreview();
  989. }, 260 );
  990. }, 100 );
  991. } )
  992. .click( function () {
  993. $( this ).select();
  994. } );
  995. } );
  996. $( this ).siblings( '.preview-cell-weight-input' ).select();
  997. } );
  998. }, this );
  999. this.trigger( 'form_loaded', this );
  1000. },
  1001. /**
  1002. * Visually scale the row widths based on the cell weights
  1003. */
  1004. scaleRowWidths: function () {
  1005. var thisDialog = this;
  1006. this.$( '.row-preview .preview-cell' ).each( function ( i, el ) {
  1007. $( el )
  1008. .css( 'width', thisDialog.row.cells[i] * 100 + "%" )
  1009. .find( '.preview-cell-weight' ).html( Math.round( thisDialog.row.cells[i] * 1000 ) / 10 );
  1010. } );
  1011. },
  1012. /**
  1013. * Get the weights from the
  1014. */
  1015. setCellsFromForm: function () {
  1016. try {
  1017. var f = {
  1018. 'cells': parseInt( this.$( '.row-set-form input[name="cells"]' ).val() ),
  1019. 'ratio': parseFloat( this.$( '.row-set-form select[name="ratio"]' ).val() ),
  1020. 'direction': this.$( '.row-set-form select[name="ratio_direction"]' ).val()
  1021. };
  1022. if ( _.isNaN( f.cells ) ) {
  1023. f.cells = 1;
  1024. }
  1025. if ( isNaN( f.ratio ) ) {
  1026. f.ratio = 1;
  1027. }
  1028. if ( f.cells < 1 ) {
  1029. f.cells = 1;
  1030. this.$( '.row-set-form input[name="cells"]' ).val( f.cells );
  1031. }
  1032. else if ( f.cells > 10 ) {
  1033. f.cells = 10;
  1034. this.$( '.row-set-form input[name="cells"]' ).val( f.cells );
  1035. }
  1036. this.$( '.row-set-form input[name="ratio"]' ).val( f.ratio );
  1037. var cells = [];
  1038. var cellCountChanged = (
  1039. this.row.cells.length !== f.cells
  1040. );
  1041. // Now, lets create some cells
  1042. var currentWeight = 1;
  1043. for ( var i = 0; i < f.cells; i ++ ) {
  1044. cells.push( currentWeight );
  1045. currentWeight *= f.ratio;
  1046. }
  1047. // Now lets make sure that the row weights add up to 1
  1048. var totalRowWeight = _.reduce( cells, function ( memo, weight ) {
  1049. return memo + weight;
  1050. } );
  1051. cells = _.map( cells, function ( cell ) {
  1052. return cell / totalRowWeight;
  1053. } );
  1054. // Don't return cells that are too small
  1055. cells = _.filter( cells, function ( cell ) {
  1056. return cell > 0.01;
  1057. } );
  1058. if ( f.direction === 'left' ) {
  1059. cells = cells.reverse();
  1060. }
  1061. this.row.cells = cells;
  1062. if ( cellCountChanged ) {
  1063. this.regenerateRowPreview();
  1064. } else {
  1065. var thisDialog = this;
  1066. // Now lets animate the cells into their new widths
  1067. this.$( '.preview-cell' ).each( function ( i, el ) {
  1068. $( el ).animate( {'width': Math.round( thisDialog.row.cells[i] * 1000 ) / 10 + "%"}, 250 );
  1069. $( el ).find( '.preview-cell-weight' ).html( Math.round( thisDialog.row.cells[i] * 1000 ) / 10 );
  1070. } );
  1071. // So the draggable handle is not hidden.
  1072. this.$( '.preview-cell' ).css( 'overflow', 'visible' );
  1073. setTimeout( function () {
  1074. thisDialog.regenerateRowPreview();
  1075. }, 260 );
  1076. }
  1077. }
  1078. catch (err) {
  1079. console.log( 'Error setting cells - ' + err.message );
  1080. }
  1081. // Remove the button primary class
  1082. this.$( '.row-set-form .so-button-row-set' ).removeClass( 'button-primary' );
  1083. },
  1084. /**
  1085. * Handle a click on the dialog left bar tab
  1086. */
  1087. tabClickHandler: function ( $t ) {
  1088. if ( $t.attr( 'href' ) === '#row-layout' ) {
  1089. this.$( '.so-panels-dialog' ).addClass( 'so-panels-dialog-has-right-sidebar' );
  1090. } else {
  1091. this.$( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  1092. }
  1093. },
  1094. /**
  1095. * Update the current model with what we have in the dialog
  1096. */
  1097. updateModel: function ( args ) {
  1098. args = _.extend( {
  1099. refresh: true,
  1100. refreshArgs: null
  1101. }, args );
  1102. // Set the cells
  1103. if( ! _.isUndefined( this.model ) ) {
  1104. this.model.setCells( this.row.cells );
  1105. }
  1106. // Update the styles if they've loaded
  1107. if ( ! _.isUndefined( this.styles ) && this.styles.stylesLoaded ) {
  1108. // This is an edit dialog, so there are styles
  1109. var style = {};
  1110. try {
  1111. style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
  1112. }
  1113. catch ( err ) {
  1114. console.log( 'Error retrieving styles - ' + err.message );
  1115. }
  1116. this.model.set( 'style', style );
  1117. }
  1118. if ( args.refresh ) {
  1119. this.builder.model.refreshPanelsData( args.refreshArgs );
  1120. }
  1121. },
  1122. /**
  1123. * Insert the new row
  1124. */
  1125. insertHandler: function () {
  1126. this.builder.addHistoryEntry( 'row_added' );
  1127. this.model = new panels.model.row();
  1128. this.updateModel();
  1129. var activeCell = this.builder.getActiveCell( {
  1130. createCell: false,
  1131. defaultPosition: 'last'
  1132. } );
  1133. var options = {};
  1134. if ( activeCell !== null ) {
  1135. options.at = this.builder.model.rows.indexOf( activeCell.row ) + 1;
  1136. }
  1137. // Set up the model and add it to the builder
  1138. this.model.collection = this.builder.model.rows;
  1139. this.builder.model.rows.add( this.model, options );
  1140. this.closeDialog();
  1141. this.builder.model.refreshPanelsData();
  1142. return false;
  1143. },
  1144. /**
  1145. * We'll just save this model and close the dialog
  1146. */
  1147. saveHandler: function () {
  1148. this.builder.addHistoryEntry( 'row_edited' );
  1149. this.updateModel();
  1150. this.closeDialog();
  1151. this.builder.model.refreshPanelsData();
  1152. return false;
  1153. },
  1154. /**
  1155. * The user clicks delete, so trigger deletion on the row model
  1156. */
  1157. deleteHandler: function () {
  1158. // Trigger a destroy on the model that will happen with a visual indication to the user
  1159. this.model.trigger( 'visual_destroy' );
  1160. this.closeDialog( {silent: true} );
  1161. return false;
  1162. },
  1163. /**
  1164. * Duplicate this row
  1165. */
  1166. duplicateHandler: function () {
  1167. this.builder.addHistoryEntry( 'row_duplicated' );
  1168. var duplicateRow = this.model.clone( this.builder.model );
  1169. this.builder.model.rows.add( duplicateRow, {
  1170. at: this.builder.model.rows.indexOf( this.model ) + 1
  1171. } );
  1172. this.closeDialog( {silent: true} );
  1173. return false;
  1174. }
  1175. } );
  1176. },{}],9:[function(require,module,exports){
  1177. var panels = window.panels, $ = jQuery;
  1178. module.exports = panels.view.dialog.extend( {
  1179. builder: null,
  1180. sidebarWidgetTemplate: _.template( $( '#siteorigin-panels-dialog-widget-sidebar-widget' ).html().panelsProcessTemplate() ),
  1181. dialogClass: 'so-panels-dialog-edit-widget',
  1182. widgetView: false,
  1183. savingWidget: false,
  1184. events: {
  1185. 'click .so-close': 'saveHandler',
  1186. 'click .so-nav.so-previous': 'navToPrevious',
  1187. 'click .so-nav.so-next': 'navToNext',
  1188. // Action handlers
  1189. 'click .so-toolbar .so-delete': 'deleteHandler',
  1190. 'click .so-toolbar .so-duplicate': 'duplicateHandler'
  1191. },
  1192. initializeDialog: function () {
  1193. var thisView = this;
  1194. this.model.on( 'change:values', this.handleChangeValues, this );
  1195. this.model.on( 'destroy', this.remove, this );
  1196. // Refresh panels data after both dialog form components are loaded
  1197. this.dialogFormsLoaded = 0;
  1198. this.on( 'form_loaded styles_loaded', function () {
  1199. this.dialogFormsLoaded ++;
  1200. if ( this.dialogFormsLoaded === 2 ) {
  1201. thisView.updateModel( {
  1202. refreshArgs: {
  1203. silent: true
  1204. }
  1205. } );
  1206. }
  1207. } );
  1208. },
  1209. /**
  1210. * Render the widget dialog.
  1211. */
  1212. render: function () {
  1213. // Render the dialog and attach it to the builder interface
  1214. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widget' ).html(), {} ) );
  1215. this.loadForm();
  1216. if ( ! _.isUndefined( panelsOptions.widgets[this.model.get( 'class' )] ) ) {
  1217. this.$( '.so-title .widget-name' ).html( panelsOptions.widgets[this.model.get( 'class' )].title );
  1218. } else {
  1219. this.$( '.so-title .widget-name' ).html( panelsOptions.loc.missing_widget.title );
  1220. }
  1221. // Now we need to attach the style window
  1222. this.styles = new panels.view.styles();
  1223. this.styles.model = this.model;
  1224. this.styles.render( 'widget', $( '#post_ID' ).val(), {
  1225. builderType: this.builder.builderType,
  1226. dialog: this
  1227. } );
  1228. var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
  1229. this.styles.attach( $rightSidebar );
  1230. // Handle the loading class
  1231. this.styles.on( 'styles_loaded', function ( hasStyles ) {
  1232. // If we have styles remove the loading spinner, else remove the whole empty sidebar.
  1233. if ( hasStyles ) {
  1234. $rightSidebar.removeClass( 'so-panels-loading' );
  1235. } else {
  1236. $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  1237. $rightSidebar.remove();
  1238. }
  1239. }, this );
  1240. $rightSidebar.addClass( 'so-panels-loading' );
  1241. },
  1242. /**
  1243. * Get the previous widget editing dialog by looking at the dom.
  1244. * @returns {*}
  1245. */
  1246. getPrevDialog: function () {
  1247. var widgets = this.builder.$( '.so-cells .cell .so-widget' );
  1248. if ( widgets.length <= 1 ) {
  1249. return false;
  1250. }
  1251. var currentIndex = widgets.index( this.widgetView.$el );
  1252. if ( currentIndex === 0 ) {
  1253. return false;
  1254. } else {
  1255. var widgetView = widgets.eq( currentIndex - 1 ).data( 'view' );
  1256. if ( _.isUndefined( widgetView ) ) {
  1257. return false;
  1258. }
  1259. return widgetView.getEditDialog();
  1260. }
  1261. },
  1262. /**
  1263. * Get the next widget editing dialog by looking at the dom.
  1264. * @returns {*}
  1265. */
  1266. getNextDialog: function () {
  1267. var widgets = this.builder.$( '.so-cells .cell .so-widget' );
  1268. if ( widgets.length <= 1 ) {
  1269. return false;
  1270. }
  1271. var currentIndex = widgets.index( this.widgetView.$el );
  1272. if ( currentIndex === widgets.length - 1 ) {
  1273. return false;
  1274. } else {
  1275. var widgetView = widgets.eq( currentIndex + 1 ).data( 'view' );
  1276. if ( _.isUndefined( widgetView ) ) {
  1277. return false;
  1278. }
  1279. return widgetView.getEditDialog();
  1280. }
  1281. },
  1282. /**
  1283. * Load the widget form from the server.
  1284. * This is called when rendering the dialog for the first time.
  1285. */
  1286. loadForm: function () {
  1287. // don't load the form if this dialog hasn't been rendered yet
  1288. if ( ! this.$( '> *' ).length ) {
  1289. return;
  1290. }
  1291. var thisView = this;
  1292. this.$( '.so-content' ).addClass( 'so-panels-loading' );
  1293. var data = {
  1294. 'action': 'so_panels_widget_form',
  1295. 'widget': this.model.get( 'class' ),
  1296. 'instance': JSON.stringify( this.model.get( 'values' ) ),
  1297. 'raw': this.model.get( 'raw' )
  1298. };
  1299. $.post(
  1300. panelsOptions.ajaxurl,
  1301. data,
  1302. function ( result ) {
  1303. // Add in the CID of the widget model
  1304. var html = result.replace( /{\$id}/g, thisView.model.cid );
  1305. // Load this content into the form
  1306. thisView.$( '.so-content' )
  1307. .removeClass( 'so-panels-loading' )
  1308. .html( html );
  1309. // Trigger all the necessary events
  1310. thisView.trigger( 'form_loaded', thisView );
  1311. // For legacy compatibility, trigger a panelsopen event
  1312. thisView.$( '.panel-dialog' ).trigger( 'panelsopen' );
  1313. // If the main dialog is closed from this point on, save the widget content
  1314. thisView.on( 'close_dialog', thisView.updateModel, thisView );
  1315. },
  1316. 'html'
  1317. );
  1318. },
  1319. /**
  1320. * Save the widget from the form to the model
  1321. */
  1322. updateModel: function ( args ) {
  1323. args = _.extend( {
  1324. refresh: true,
  1325. refreshArgs: null
  1326. }, args );
  1327. // Get the values from the form and assign the new values to the model
  1328. this.savingWidget = true;
  1329. if ( ! this.model.get( 'missing' ) ) {
  1330. // Only get the values for non missing widgets.
  1331. var values = this.getFormValues();
  1332. if ( _.isUndefined( values.widgets ) ) {
  1333. values = {};
  1334. } else {
  1335. values = values.widgets;
  1336. values = values[Object.keys( values )[0]];
  1337. }
  1338. this.model.setValues( values );
  1339. this.model.set( 'raw', true ); // We've saved from the widget form, so this is now raw
  1340. }
  1341. if ( this.styles.stylesLoaded ) {
  1342. // If the styles view has loaded
  1343. var style = {};
  1344. try {
  1345. style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
  1346. }
  1347. catch ( e ) {
  1348. }
  1349. this.model.set( 'style', style );
  1350. }
  1351. this.savingWidget = false;
  1352. if ( args.refresh ) {
  1353. this.builder.model.refreshPanelsData( args.refreshArgs );
  1354. }
  1355. },
  1356. /**
  1357. *
  1358. */
  1359. handleChangeValues: function () {
  1360. if ( ! this.savingWidget ) {
  1361. // Reload the form when we've changed the model and we're not currently saving from the form
  1362. this.loadForm();
  1363. }
  1364. },
  1365. /**
  1366. * Save a history entry for this widget. Called when the dialog is closed.
  1367. */
  1368. saveHandler: function () {
  1369. this.builder.addHistoryEntry( 'widget_edited' );
  1370. this.closeDialog();
  1371. },
  1372. /**
  1373. * When the user clicks delete.
  1374. *
  1375. * @returns {boolean}
  1376. */
  1377. deleteHandler: function () {
  1378. this.model.trigger( 'visual_destroy' );
  1379. this.closeDialog( {silent: true} );
  1380. this.builder.model.refreshPanelsData();
  1381. return false;
  1382. },
  1383. duplicateHandler: function () {
  1384. this.model.trigger( 'user_duplicate' );
  1385. this.closeDialog( {silent: true} );
  1386. this.builder.model.refreshPanelsData();
  1387. return false;
  1388. }
  1389. } );
  1390. },{}],10:[function(require,module,exports){
  1391. var panels = window.panels, $ = jQuery;
  1392. module.exports = panels.view.dialog.extend( {
  1393. builder: null,
  1394. widgetTemplate: _.template( $( '#siteorigin-panels-dialog-widgets-widget' ).html().panelsProcessTemplate() ),
  1395. filter: {},
  1396. dialogClass: 'so-panels-dialog-add-widget',
  1397. events: {
  1398. 'click .so-close': 'closeDialog',
  1399. 'click .widget-type': 'widgetClickHandler',
  1400. 'keyup .so-sidebar-search': 'searchHandler'
  1401. },
  1402. /**
  1403. * Initialize the widget adding dialog
  1404. */
  1405. initializeDialog: function () {
  1406. this.on( 'open_dialog', function () {
  1407. this.filter.search = '';
  1408. this.filterWidgets( this.filter );
  1409. }, this );
  1410. this.on( 'open_dialog_complete', function () {
  1411. // Clear the search and re-filter the widgets when we open the dialog
  1412. this.$( '.so-sidebar-search' ).val( '' ).focus();
  1413. this.balanceWidgetHeights();
  1414. } );
  1415. // We'll implement a custom tab click handler
  1416. this.on( 'tab_click', this.tabClickHandler, this );
  1417. },
  1418. render: function () {
  1419. // Render the dialog and attach it to the builder interface
  1420. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widgets' ).html(), {} ) );
  1421. // Add all the widgets
  1422. _.each( panelsOptions.widgets, function ( widget ) {
  1423. var $w = $( this.widgetTemplate( {
  1424. title: widget.title,
  1425. description: widget.description
  1426. } ) );
  1427. if ( _.isUndefined( widget.icon ) ) {
  1428. widget.icon = 'dashicons dashicons-admin-generic';
  1429. }
  1430. $( '<span class="widget-icon" />' ).addClass( widget.icon ).prependTo( $w.find( '.widget-type-wrapper' ) );
  1431. $w.data( 'class', widget.class ).appendTo( this.$( '.widget-type-list' ) );
  1432. }, this );
  1433. // Add the sidebar tabs
  1434. var tabs = this.$( '.so-sidebar-tabs' );
  1435. _.each( panelsOptions.widget_dialog_tabs, function ( tab ) {
  1436. $( this.dialogTabTemplate( {'title': tab.title} ) ).data( {
  1437. 'message': tab.message,
  1438. 'filter': tab.filter
  1439. } ).appendTo( tabs );
  1440. }, this );
  1441. // We'll be using tabs, so initialize them
  1442. this.initTabs();
  1443. var thisDialog = this;
  1444. $( window ).resize( function () {
  1445. thisDialog.balanceWidgetHeights();
  1446. } );
  1447. },
  1448. /**
  1449. * Handle a tab being clicked
  1450. */
  1451. tabClickHandler: function ( $t ) {
  1452. // Get the filter from the tab, and filter the widgets
  1453. this.filter = $t.parent().data( 'filter' );
  1454. this.filter.search = this.$( '.so-sidebar-search' ).val();
  1455. var message = $t.parent().data( 'message' );
  1456. if ( _.isEmpty( message ) ) {
  1457. message = '';
  1458. }
  1459. this.$( '.so-toolbar .so-status' ).html( message );
  1460. this.filterWidgets( this.filter );
  1461. return false;
  1462. },
  1463. /**
  1464. * Handle changes to the search value
  1465. */
  1466. searchHandler: function ( e ) {
  1467. this.filter.search = $( e.target ).val();
  1468. this.filterWidgets( this.filter );
  1469. },
  1470. /**
  1471. * Filter the widgets that we're displaying
  1472. * @param filter
  1473. */
  1474. filterWidgets: function ( filter ) {
  1475. if ( _.isUndefined( filter ) ) {
  1476. filter = {};
  1477. }
  1478. if ( _.isUndefined( filter.groups ) ) {
  1479. filter.groups = '';
  1480. }
  1481. this.$( '.widget-type-list .widget-type' ).each( function () {
  1482. var $$ = $( this ), showWidget;
  1483. var widgetClass = $$.data( 'class' );
  1484. var widgetData = (
  1485. ! _.isUndefined( panelsOptions.widgets[widgetClass] )
  1486. ) ? panelsOptions.widgets[widgetClass] : null;
  1487. if ( _.isEmpty( filter.groups ) ) {
  1488. // This filter doesn't specify groups, so show all
  1489. showWidget = true;
  1490. } else if ( widgetData !== null && ! _.isEmpty( _.intersection( filter.groups, panelsOptions.widgets[widgetClass].groups ) ) ) {
  1491. // This widget is in the filter group
  1492. showWidget = true;
  1493. } else {
  1494. // This widget is not in the filter group
  1495. showWidget = false;
  1496. }
  1497. // This can probably be done with a more intelligent operator
  1498. if ( showWidget ) {
  1499. if ( ! _.isUndefined( filter.search ) && filter.search !== '' ) {
  1500. // Check if the widget title contains the search term
  1501. if ( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) === - 1 ) {
  1502. showWidget = false;
  1503. }
  1504. }
  1505. }
  1506. if ( showWidget ) {
  1507. $$.show();
  1508. } else {
  1509. $$.hide();
  1510. }
  1511. } );
  1512. // Balance the tags after filtering
  1513. this.balanceWidgetHeights();
  1514. },
  1515. /**
  1516. * Add the widget to the current builder
  1517. *
  1518. * @param e
  1519. */
  1520. widgetClickHandler: function ( e ) {
  1521. // Add the history entry
  1522. this.builder.addHistoryEntry( 'widget_added' );
  1523. var $w = $( e.currentTarget );
  1524. var widget = new panels.model.widget( {
  1525. class: $w.data( 'class' )
  1526. } );
  1527. // Add the widget to the cell model
  1528. widget.cell = this.builder.getActiveCell();
  1529. widget.cell.widgets.add( widget );
  1530. this.closeDialog();
  1531. this.bu