first commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
jQuery(function($){
|
||||
var initialized = false,
|
||||
changeTimeoutId = 0,
|
||||
randomIdIncrement = 0,
|
||||
localized = _fw_backend_customizer_localized,
|
||||
/**
|
||||
* @type {Object} {'#options_wrapper_id':'~'}
|
||||
*/
|
||||
pendingChanges = {},
|
||||
/**
|
||||
* Extract all input values within option and save them to the customizer input (to trigger preview update)
|
||||
*/
|
||||
processPendingChanges = function(){
|
||||
$.each(pendingChanges, function(optionsWrapperId){
|
||||
var $optionsWrapper = $('#'+ optionsWrapperId),
|
||||
$input = $optionsWrapper.closest('.fw-backend-customizer-option')
|
||||
.find('> input.fw-backend-customizer-option-input'),
|
||||
newValue = JSON.stringify(fixSerializedValues(
|
||||
$optionsWrapper.find(':input').serializeArray()
|
||||
));
|
||||
|
||||
if ($input.val() === newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$input.val(newValue).trigger('change');
|
||||
});
|
||||
|
||||
pendingChanges = {};
|
||||
},
|
||||
fixSerializedValues = function(values) {
|
||||
var inputNameToIndex = {},
|
||||
fixedValues = [];
|
||||
|
||||
/**
|
||||
* Traverse reversed array to leave only the last values.
|
||||
* This is how _POST works, if you have
|
||||
* fw_options[option_name][x]: 3
|
||||
* fw_options[option_name][x]: 7
|
||||
* the last one "wins" and the value of $_POST['fw_options']['option_name']['x'] will be 7
|
||||
*/
|
||||
for (var i = values.length - 1; i >= 0; i--) {
|
||||
if (values[i].name.slice(-2) === '[]') {
|
||||
// this will be sent in _POST as array
|
||||
} else {
|
||||
if (typeof inputNameToIndex[values[i].name] === 'undefined') {
|
||||
inputNameToIndex[values[i].name] = i;
|
||||
} else {
|
||||
continue; // skip if already added (the last overwrites others)
|
||||
}
|
||||
}
|
||||
|
||||
fixedValues.push(values[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The array was traversed in revers order, now restore the initial order
|
||||
*/
|
||||
return fixedValues.reverse();
|
||||
},
|
||||
init = function(){
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate all <input class="fw-backend-customizer-option-input" ... /> with (initial) options values
|
||||
*/
|
||||
$('#customize-theme-controls .fw-backend-customizer-option').each(function(){
|
||||
$(this).find('> input.fw-backend-customizer-option-input').val(
|
||||
JSON.stringify(fixSerializedValues(
|
||||
$(this).find('> .fw-backend-customizer-option-inner :input').serializeArray()
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* When something may be changed, removed, added; add to pending changes
|
||||
*/
|
||||
$('#customize-theme-controls').on(
|
||||
'change keyup click paste',
|
||||
'.fw-backend-customizer-option > .fw-backend-customizer-option-inner > .fw-backend-option > .fw-backend-option-input',
|
||||
function(e){
|
||||
clearTimeout(changeTimeoutId);
|
||||
|
||||
{
|
||||
var optionsWrapperId = $(this).attr('id');
|
||||
|
||||
if (!optionsWrapperId) {
|
||||
optionsWrapperId = 'rnid-'+ (++randomIdIncrement);
|
||||
$(this).attr('id', optionsWrapperId);
|
||||
}
|
||||
|
||||
pendingChanges[optionsWrapperId] = '~';
|
||||
}
|
||||
|
||||
changeTimeoutId = setTimeout(
|
||||
processPendingChanges,
|
||||
/**
|
||||
* Let css animations finish,
|
||||
* to prevent block/glitch in the middle of the animation when the iframe will reload.
|
||||
* Bigger than 300, which most of the css animations are.
|
||||
*/
|
||||
localized.change_timeout
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
initialized = true;
|
||||
};
|
||||
|
||||
fwEvents.one('fw:options:init', function(){
|
||||
setTimeout(
|
||||
init,
|
||||
40 // must be later than first 'fw:options:init' on body http://bit.ly/1F1dDUZ
|
||||
);
|
||||
});
|
||||
});
|
||||
262
wp-content/plugins/unyson/framework/static/js/backend-options.js
Normal file
262
wp-content/plugins/unyson/framework/static/js/backend-options.js
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Included on pages where backend options are rendered
|
||||
*/
|
||||
|
||||
var fwBackendOptions = {
|
||||
/**
|
||||
* @deprecated Tabs are lazy loaded https://github.com/ThemeFuse/Unyson/issues/1174
|
||||
*/
|
||||
openTab: function(tabId) { console.warn('deprecated'); }
|
||||
};
|
||||
|
||||
jQuery(document).ready(function($){
|
||||
var localized = _fw_backend_options_localized;
|
||||
|
||||
/**
|
||||
* Functions
|
||||
*/
|
||||
{
|
||||
/**
|
||||
* Make fw-postbox to close/open on click
|
||||
*
|
||||
* (fork from /wp-admin/js/postbox.js)
|
||||
*/
|
||||
function addPostboxToggles($boxes) {
|
||||
/** Remove events added by /wp-admin/js/postbox.js */
|
||||
$boxes.find('h2, h3, .handlediv, .hndle').off('click.postboxes');
|
||||
|
||||
var eventNamespace = '.fw-backend-postboxes';
|
||||
|
||||
$boxes.find('.postbox-header .hndle, .postbox-header .handlediv').on('mouseover', function () {
|
||||
$(this).off('click.postboxes');
|
||||
})
|
||||
|
||||
// make postboxes to close/open on click
|
||||
$boxes.off('click'+ eventNamespace); // remove already attached, just to be sure, prevent multiple execution
|
||||
$boxes.find('.postbox-header .hndle, .postbox-header .handlediv').on( 'click', function( e ) {
|
||||
|
||||
var $box = $(this).closest('.fw-postbox');
|
||||
|
||||
if ($box.parent().is('.fw-backend-postboxes') && !$box.siblings().length) {
|
||||
// Do not close if only one box https://github.com/ThemeFuse/Unyson/issues/1094
|
||||
$box.removeClass('closed');
|
||||
} else {
|
||||
$box.toggleClass('closed');
|
||||
}
|
||||
|
||||
var isClosed = $box.hasClass('closed');
|
||||
|
||||
$box.trigger('fw:box:'+ (isClosed ? 'close' : 'open'));
|
||||
$box.trigger('fw:box:toggle-closed', {isClosed: isClosed});
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove box header if title is empty */
|
||||
function hideBoxEmptyTitles($boxes) {
|
||||
$boxes.find('> .hndle > span').each(function(){
|
||||
var $this = $(this);
|
||||
|
||||
if (!$.trim($this.html()).length) {
|
||||
$this.closest('.postbox').addClass('fw-postbox-without-name');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Init tabs */
|
||||
(function(){
|
||||
var htmlAttrName = 'data-fw-tab-html',
|
||||
initTab = function($tab) {
|
||||
var html;
|
||||
|
||||
if (html = $tab.attr(htmlAttrName)) {
|
||||
fwEvents.trigger('fw:options:init', {
|
||||
$elements: $tab.removeAttr(htmlAttrName).html(html),
|
||||
/**
|
||||
* Sometimes we want to perform some action just when
|
||||
* lazy tabs are rendered. It's important in those cases
|
||||
* to distinguish regular fw:options:init events from
|
||||
* the ones that will render tabs. Passing by this little
|
||||
* detail may break some widgets because fw:options:init
|
||||
* event may be fired even when tabs are not yet rendered.
|
||||
*
|
||||
* That's how you can be sure that you'll run a piece
|
||||
* of code just when tabs will be arround 100%.
|
||||
*
|
||||
* fwEvents.on('fw:options:init', function (data) {
|
||||
* if (! data.lazyTabsUpdated) {
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* // Do your business
|
||||
* });
|
||||
*
|
||||
*/
|
||||
lazyTabsUpdated: true
|
||||
});
|
||||
}
|
||||
},
|
||||
initAllTabs = function ($el) {
|
||||
var selector = '.fw-options-tab[' + htmlAttrName + ']', $tabs;
|
||||
|
||||
// fixes https://github.com/ThemeFuse/Unyson/issues/1634
|
||||
$el.each(function(){
|
||||
if ($(this).is(selector)) {
|
||||
initTab($(this));
|
||||
}
|
||||
});
|
||||
|
||||
// initialized tabs can contain tabs, so init recursive until nothing is found
|
||||
while (($tabs = $el.find(selector)).length) {
|
||||
$tabs.each(function(){ initTab($(this)); });
|
||||
}
|
||||
};
|
||||
|
||||
fwEvents.on('fw:options:init:tabs', function (data) {
|
||||
initAllTabs(data.$elements);
|
||||
});
|
||||
|
||||
fwEvents.on('fw:options:init', function (data) {
|
||||
var $tabs = data.$elements.find('.fw-options-tabs-wrapper:not(.initialized)');
|
||||
|
||||
if (localized.lazy_tabs) {
|
||||
$tabs.tabs({
|
||||
create: function (event, ui) {
|
||||
initTab(ui.panel);
|
||||
},
|
||||
activate: function (event, ui) {
|
||||
initTab(ui.newPanel);
|
||||
ui.newPanel.closest('.fw-options-tabs-contents')[0].scrollTop = 0
|
||||
}
|
||||
});
|
||||
|
||||
$tabs
|
||||
.closest('form')
|
||||
.off('submit.fw-tabs')
|
||||
.on('submit.fw-tabs', function () {
|
||||
if (!$(this).hasClass('prevent-all-tabs-init')) {
|
||||
// All options needs to be present in html to be sent in POST on submit
|
||||
initAllTabs($(this));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$tabs.tabs({
|
||||
activate: function (event, ui) {
|
||||
ui.newPanel.closest('.fw-options-tabs-contents')[0].scrollTop = 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$tabs.each(function () {
|
||||
var $this = $(this);
|
||||
|
||||
if (!$this.parent().closest('.fw-options-tabs-wrapper').length) {
|
||||
// add special class to first level tabs
|
||||
$this.addClass('fw-options-tabs-first-level');
|
||||
}
|
||||
});
|
||||
|
||||
$tabs.addClass('initialized');
|
||||
});
|
||||
})();
|
||||
|
||||
/** Init boxes */
|
||||
fwEvents.on('fw:options:init', function (data) {
|
||||
var $boxes = data.$elements.find('.fw-postbox:not(.initialized)');
|
||||
|
||||
hideBoxEmptyTitles(
|
||||
$boxes.filter('.fw-backend-postboxes > .fw-postbox')
|
||||
);
|
||||
|
||||
addPostboxToggles($boxes);
|
||||
|
||||
/**
|
||||
* leave open only first boxes
|
||||
*/
|
||||
$boxes
|
||||
.filter('.fw-backend-postboxes > .fw-postbox:not(.fw-postbox-without-name):not(:first-child):not(.prevent-auto-close)')
|
||||
.addClass('closed');
|
||||
|
||||
$boxes.addClass('initialized');
|
||||
|
||||
// trigger on box custom event for others to do something after box initialized
|
||||
$boxes.trigger('fw-options-box:initialized');
|
||||
});
|
||||
|
||||
/** Init options */
|
||||
fwEvents.on('fw:options:init', function (data) {
|
||||
data.$elements.find('.fw-backend-option:not(.initialized)')
|
||||
// do nothing, just a the initialized class to make the fadeIn css animation effect
|
||||
.addClass('initialized');
|
||||
});
|
||||
|
||||
/** Fixes */
|
||||
fwEvents.on('fw:options:init', function (data) {
|
||||
{
|
||||
var eventNamespace = '.fw-backend-postboxes';
|
||||
|
||||
data.$elements.find('.postbox:not(.fw-postbox) .fw-option')
|
||||
.closest('.postbox:not(.fw-postbox)')
|
||||
|
||||
/**
|
||||
* Add special class to first level postboxes that contains framework options (on post edit page)
|
||||
*/
|
||||
.addClass('postbox-with-fw-options')
|
||||
|
||||
/**
|
||||
* Prevent event to be propagated to first level WordPress sortable (on edit post page)
|
||||
* If not prevented, boxes within options can be dragged out of parent box to first level boxes
|
||||
*/
|
||||
.off('mousedown'+ eventNamespace) // remove already attached (happens when this script is executed multiple times on the same elements)
|
||||
.on('mousedown'+ eventNamespace, '.fw-postbox > .hndle, .fw-postbox > .handlediv', function(e){
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* disable sortable (drag/drop) for postboxes created by framework options
|
||||
* (have no sense, the order is not saved like for first level boxes on edit post page)
|
||||
*/
|
||||
{
|
||||
var $sortables = data.$elements
|
||||
.find('.postbox:not(.fw-postbox) .fw-postbox, .fw-options-tabs-wrapper .fw-postbox')
|
||||
.closest('.fw-backend-postboxes')
|
||||
.not('.fw-sortable-disabled');
|
||||
|
||||
$sortables.each(function(){
|
||||
try {
|
||||
$(this).sortable('destroy');
|
||||
} catch (e) {
|
||||
// happens when not initialized
|
||||
}
|
||||
});
|
||||
|
||||
$sortables.addClass('fw-sortable-disabled');
|
||||
}
|
||||
|
||||
/** hide bottom border from last option inside box */
|
||||
{
|
||||
data.$elements.find('.postbox-with-fw-options > .inside, .fw-postbox > .inside')
|
||||
.append('<div class="fw-backend-options-last-border-hider"></div>');
|
||||
}
|
||||
|
||||
hideBoxEmptyTitles(
|
||||
data.$elements.find('.postbox-with-fw-options')
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Help tips (i)
|
||||
*/
|
||||
(function(){
|
||||
fwEvents.on('fw:options:init', function (data) {
|
||||
var $helps = data.$elements.find('.fw-option-help:not(.initialized)');
|
||||
|
||||
fw.qtip($helps);
|
||||
|
||||
$helps.addClass('initialized');
|
||||
});
|
||||
})();
|
||||
|
||||
$('#side-sortables').addClass('fw-force-xs');
|
||||
});
|
||||
271
wp-content/plugins/unyson/framework/static/js/fw-events.js
Normal file
271
wp-content/plugins/unyson/framework/static/js/fw-events.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Listen and trigger custom events to communicate between javascript components
|
||||
*/
|
||||
var fwEvents = new (function(){
|
||||
var _events = {};
|
||||
var currentIndentation = 1;
|
||||
var debug = false;
|
||||
|
||||
this.countAll = function (topic) {
|
||||
return _events[topic];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make log helper public
|
||||
*
|
||||
* @param {String} [message]
|
||||
* @param {Object} [data]
|
||||
*/
|
||||
this.log = log;
|
||||
|
||||
/**
|
||||
* Enable/Disable Debug
|
||||
* @param {Boolean} enabled
|
||||
*/
|
||||
this.debug = function(enabled) {
|
||||
debug = Boolean(enabled);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add event listener
|
||||
*
|
||||
* @param event {String | Object}
|
||||
* Can be a:
|
||||
* - single event: 'event1'
|
||||
* - space separated event list: 'event1 event2 event2'
|
||||
* - an object: {event1: function () {}, event2: function () {}}
|
||||
*
|
||||
* @param callback {Function}
|
||||
*/
|
||||
this.on = function(topicStringOrObject, listener) {
|
||||
objectMap(
|
||||
splitTopicStringOrObject(topicStringOrObject, listener),
|
||||
function (eventName, listener) {
|
||||
(_events[eventName] || (_events[eventName] = [])).push(
|
||||
listener
|
||||
);
|
||||
|
||||
debug && log('✚ ' + eventName);
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as .on(), but callback will executed only once
|
||||
*/
|
||||
this.one = function(topicStringOrObject, listener) {
|
||||
objectMap(
|
||||
splitTopicStringOrObject(topicStringOrObject, listener),
|
||||
function (eventName, listener) {
|
||||
(_events[eventName] || (_events[eventName] = [])).push(
|
||||
once(listener)
|
||||
);
|
||||
|
||||
debug && log('✚ [' + eventName +']');
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
|
||||
// https://github.com/jashkenas/underscore/blob/8fc7032295d60aff3620ef85d4aa6549a55688a0/underscore.js#L946
|
||||
function once(func) {
|
||||
var memo;
|
||||
|
||||
var times = 2;
|
||||
|
||||
return function() {
|
||||
if (--times > 0) {
|
||||
memo = func.apply(this, arguments);
|
||||
}
|
||||
|
||||
if (times <= 1) func = null;
|
||||
|
||||
return memo;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* In order to remove one single listener you should give as an argument
|
||||
* the same callback function. If you want to remove *all* listeners from
|
||||
* a particular event you should not pass the second argument.
|
||||
*
|
||||
* @param topicStringOrObject {String | Object}
|
||||
* @param listener {Function | false}
|
||||
*/
|
||||
this.off = function(topicStringOrObject, listener) {
|
||||
objectMap(
|
||||
splitTopicStringOrObject(topicStringOrObject, listener),
|
||||
function (eventName, listener) {
|
||||
if (_events[eventName]) {
|
||||
if (listener) {
|
||||
_events[eventName].splice(
|
||||
_events[eventName].indexOf(listener) >>> 0,
|
||||
1
|
||||
);
|
||||
} else {
|
||||
_events[eventName] = [];
|
||||
}
|
||||
|
||||
debug && log('✖ ' + eventName);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger an event. In case you provide multiple events via space-separated
|
||||
* string or an object of events it will execute listeners for each event
|
||||
* separatedly. You can use the "all" event to trigger all events.
|
||||
*
|
||||
* @param topicStringOrObject {String | Object}
|
||||
* @param data {Object}
|
||||
*/
|
||||
this.trigger = function(eventName, data) {
|
||||
objectMap(
|
||||
splitTopicStringOrObject(eventName),
|
||||
function (eventName) {
|
||||
log('╭─ '+ eventName, data);
|
||||
|
||||
changeIndentation(+1);
|
||||
|
||||
try {
|
||||
// TODO: REFACTOR THAT!!!!!!!!!
|
||||
// Maybe this is an occasion for using 'all' event???
|
||||
if (eventName === 'fw:options:init') {
|
||||
fw.options.startListeningToEvents(
|
||||
data.$elements || document.body
|
||||
)
|
||||
}
|
||||
|
||||
(_events[eventName] || []).map(dispatchSingleEvent);
|
||||
(_events['all'] || []).map(dispatchSingleEvent);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"%c [Events] Exception raised. Please contact support in https://github.com/ThemeFuse/Unyson/issues/new. Don't forget to attach this stack trace to the issue.",
|
||||
"color: red; font-weight: bold;"
|
||||
);
|
||||
|
||||
if (typeof console !== 'undefined') {
|
||||
console.error(e)
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
changeIndentation(-1);
|
||||
|
||||
log('╰─ '+ eventName, data);
|
||||
|
||||
function dispatchSingleEvent (listenerDescriptor) {
|
||||
if (! listenerDescriptor) return;
|
||||
|
||||
listenerDescriptor.call(
|
||||
window,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
|
||||
function changeIndentation(increment) {
|
||||
if (typeof increment != 'undefined') {
|
||||
currentIndentation += (increment > 0 ? +1 : -1);
|
||||
}
|
||||
|
||||
if (currentIndentation < 0) {
|
||||
currentIndentation = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if an event has listeners
|
||||
* @param {String} [event]
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.hasListeners = function(eventName) {
|
||||
if (! _events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (_events[eventName] || []).length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Probably split string into general purpose object representation for
|
||||
* event names and listeners. This function leaves objects un-modified.
|
||||
*
|
||||
* @param topicStringOrObject {String | Object}
|
||||
* @param listener {Function | false}
|
||||
*
|
||||
* @returns {Object} {
|
||||
* eventname: listener,
|
||||
* otherevent: listener
|
||||
* }
|
||||
*/
|
||||
function splitTopicStringOrObject (topicStringOrObject, listener) {
|
||||
if (typeof topicStringOrObject !== 'string') {
|
||||
return topicStringOrObject;
|
||||
}
|
||||
|
||||
var arrayOfEvents = topicStringOrObject.replace(
|
||||
/\s\s+/g, ' '
|
||||
).trim().split(' ');
|
||||
|
||||
var len = arrayOfEvents.length;
|
||||
|
||||
var listenerDescriptor = Object.create(null);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
listenerDescriptor[arrayOfEvents[i]] = listener;
|
||||
}
|
||||
|
||||
return listenerDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new object with the predicate applied to each value
|
||||
* objectMap({a: 3, b: 5, c: 9}, (key, value) => value + 1); // {a: 4, b: 6, c: 10}
|
||||
* objectMap({a: 3, b: 5, c: 9}, (key, value) => key); // {a: 'a', b: 'b', c: 'c'}
|
||||
* objectMap({a: 3, b: 5, c: 9}, (key, value) => key + value); // {a: 'a3', b: 'b5', c: 'c9'}
|
||||
*
|
||||
* https://github.com/angus-c/just/tree/master/packages/object-map
|
||||
*/
|
||||
function objectMap(obj, predicate) {
|
||||
var result = {};
|
||||
var keys = Object.keys(obj);
|
||||
var len = keys.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var key = keys[i];
|
||||
result[key] = predicate(key, obj[key]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function log(message, data) {
|
||||
if (! debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data != 'undefined') {
|
||||
console.log('[Event] ' + getIndentation() + message, '─', data);
|
||||
} else {
|
||||
console.log('[Event] ' + getIndentation() + message);
|
||||
}
|
||||
|
||||
function getIndentation() {
|
||||
return new Array(currentIndentation).join('│ ');
|
||||
}
|
||||
}
|
||||
})();
|
||||
271
wp-content/plugins/unyson/framework/static/js/fw-form-helpers.js
Normal file
271
wp-content/plugins/unyson/framework/static/js/fw-form-helpers.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* FW_Form helpers
|
||||
* Dependencies: jQuery
|
||||
* Note: You can include this script in frontend (for e.g. to make you contact forms ajax submittable)
|
||||
*/
|
||||
|
||||
var fwForm = {
|
||||
/**
|
||||
* Make forms ajax submittable
|
||||
* @param {Object} [opts] You can overwrite any
|
||||
*/
|
||||
initAjaxSubmit: function(opts) {
|
||||
var opts = jQuery.extend({
|
||||
selector: 'form[data-fw-form-id]',
|
||||
ajaxUrl: (typeof ajaxurl != 'undefined')
|
||||
? ajaxurl
|
||||
: ((typeof fwAjaxUrl != 'undefined')
|
||||
? fwAjaxUrl // wp_localize_script('fw-form-helpers', 'fwAjaxUrl', admin_url( 'admin-ajax.php', 'relative' ));
|
||||
: '/wp-admin/admin-ajax.php'
|
||||
),
|
||||
loading: function (elements, show) {
|
||||
elements.$form.css('position', 'relative');
|
||||
elements.$form.find('> .fw-form-loading').remove();
|
||||
|
||||
if (show) {
|
||||
elements.$form.append(
|
||||
'<div'+
|
||||
' class="fw-form-loading"'+
|
||||
' style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.1);"'+
|
||||
'></div>'
|
||||
);
|
||||
}
|
||||
},
|
||||
afterSubmitDelay: function(elements){},
|
||||
onErrors: function (elements, data) {
|
||||
if (isAdmin) {
|
||||
fwForm.backend.showFlashMessages(
|
||||
fwForm.backend.renderFlashMessages({error: data.errors})
|
||||
);
|
||||
} else {
|
||||
// Frontend
|
||||
jQuery.each(data.errors, function (inputName, message) {
|
||||
message = '<p class="form-error" style="color: #9b2922;">{message}</p>'
|
||||
.replace('{message}', message);
|
||||
|
||||
var $input = elements.$form.find('[name="' + inputName + '"]').last();
|
||||
|
||||
if (!$input.length) {
|
||||
// maybe input name has array format, try to find by prefix: name[
|
||||
$input = elements.$form.find('[name^="'+ inputName +'["]').last();
|
||||
}
|
||||
|
||||
if ($input.length) {
|
||||
// error message under input
|
||||
$input.parent().after(message);
|
||||
} else {
|
||||
// if input not found, show message in form
|
||||
elements.$form.prepend(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
hideErrors: function (elements) {
|
||||
elements.$form.find('.form-error').remove();
|
||||
},
|
||||
onAjaxError: function(elements, data) {
|
||||
console.error(data.jqXHR, data.textStatus, data.errorThrown);
|
||||
alert('Ajax error (more details in console)');
|
||||
},
|
||||
onSuccess: function (elements, ajaxData) {
|
||||
if (isAdmin) {
|
||||
fwForm.backend.showFlashMessages(
|
||||
fwForm.backend.renderFlashMessages(ajaxData.flash_messages)
|
||||
);
|
||||
} else {
|
||||
var html = fwForm.frontend.renderFlashMessages(ajaxData.flash_messages);
|
||||
|
||||
if (!html.length) {
|
||||
html = '<p>Success</p>';
|
||||
}
|
||||
|
||||
elements.$form.fadeOut(function(){
|
||||
elements.$form.html(html).fadeIn();
|
||||
});
|
||||
|
||||
// prevent multiple submit
|
||||
elements.$form.on('submit', function(e){ e.preventDefault(); e.stopPropagation(); });
|
||||
}
|
||||
}
|
||||
}, opts || {}),
|
||||
isAdmin = (typeof adminpage != 'undefined' && jQuery(document.body).hasClass('wp-admin')),
|
||||
isBusy = false;
|
||||
|
||||
jQuery(document.body).on('submit', opts.selector, function(e){
|
||||
e.preventDefault();
|
||||
|
||||
if (isBusy) {
|
||||
console.warn('Working... Try again later.');
|
||||
return;
|
||||
}
|
||||
|
||||
var $form = jQuery(this);
|
||||
|
||||
if (!$form.is('form[data-fw-form-id]')) {
|
||||
console.error('This is not a FW_Form', 'Selector:'. opts.selector, 'Form:', $form);
|
||||
return;
|
||||
}
|
||||
|
||||
// get submit button
|
||||
{
|
||||
var $submitButton = $form.find(':submit:focus');
|
||||
|
||||
if (!$submitButton.length) {
|
||||
// in case you use this solution http://stackoverflow.com/a/5721762
|
||||
$submitButton = $form.find('[clicked]:submit');
|
||||
}
|
||||
|
||||
// make sure to remove the "clicked" attribute to prevent accidental settings reset
|
||||
$form.find('[clicked]:submit').removeAttr('clicked');
|
||||
}
|
||||
|
||||
var elements = {
|
||||
$form: $form,
|
||||
$submitButton: $submitButton
|
||||
};
|
||||
|
||||
opts.hideErrors(elements);
|
||||
|
||||
var delaySubmit = parseInt(
|
||||
opts.loading(
|
||||
elements,
|
||||
/**
|
||||
* If you want to submit your ajaxified Theme Settings form without
|
||||
* any notification for the user add class fw-silent-submit to
|
||||
* the form element itself. This class will be removed
|
||||
* automatically after this particular submit, so that popup will
|
||||
* show when the user will press Submit button next time.
|
||||
*/
|
||||
! $form.hasClass('fw-silent-submit')
|
||||
)
|
||||
);
|
||||
delaySubmit = (isNaN(delaySubmit) || delaySubmit < 0) ? 0 : delaySubmit;
|
||||
|
||||
$form.removeClass('fw-silent-submit');
|
||||
|
||||
isBusy = true;
|
||||
|
||||
setTimeout(function(){
|
||||
if (delaySubmit) {
|
||||
opts.afterSubmitDelay(elements);
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: opts.ajaxUrl,
|
||||
data: $form.serialize() + (
|
||||
$submitButton.length
|
||||
? '&'+ $submitButton.attr('name') +'='+ $submitButton.attr('value')
|
||||
: ''
|
||||
),
|
||||
dataType: 'json'
|
||||
}).done(function(r){
|
||||
isBusy = false;
|
||||
opts.loading(elements, false);
|
||||
|
||||
if (r.success) {
|
||||
opts.onSuccess(elements, r.data);
|
||||
} else {
|
||||
opts.onErrors(elements, r.data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown){
|
||||
isBusy = false;
|
||||
opts.loading(elements, false);
|
||||
opts.onAjaxError(elements, {
|
||||
jqXHR: jqXHR,
|
||||
textStatus: textStatus,
|
||||
errorThrown: errorThrown
|
||||
});
|
||||
});
|
||||
}, delaySubmit);
|
||||
});
|
||||
},
|
||||
backend: {
|
||||
showFlashMessages: function(messagesHtml) {
|
||||
var $pageTitle = jQuery('.wrap h2:first');
|
||||
|
||||
while ($pageTitle.next().is('.fw-flash-messages, .fw-flash-message, .updated, .update-nag, .error')) {
|
||||
$pageTitle.next().remove();
|
||||
}
|
||||
|
||||
$pageTitle.after('<div class="fw-flash-messages">'+ messagesHtml +'</div>');
|
||||
|
||||
jQuery(document.body).animate({scrollTop: 0}, 300);
|
||||
},
|
||||
/**
|
||||
* Html structure should be the same as generated by FW_Flash_Messages::_print_backend()
|
||||
* @param {Object} flashMessages
|
||||
* @returns {string}
|
||||
*/
|
||||
renderFlashMessages: function(flashMessages) {
|
||||
var html = [],
|
||||
typeHtml = [],
|
||||
messageClass = '';
|
||||
|
||||
jQuery.each(flashMessages, function(type, messages){
|
||||
typeHtml = [];
|
||||
|
||||
switch (type) {
|
||||
case 'error':
|
||||
messageClass = 'error';
|
||||
break;
|
||||
case 'warning':
|
||||
messageClass = 'update-nag';
|
||||
break;
|
||||
default:
|
||||
messageClass = 'updated';
|
||||
}
|
||||
|
||||
jQuery.each(messages, function(messageId, message){
|
||||
typeHtml.push('<div class="'+ messageClass +' fw-flash-message"><p>'+ message +'</p></div>');
|
||||
});
|
||||
|
||||
if (typeHtml.length) {
|
||||
html.push(
|
||||
'<div class="fw-flash-type-'+ type +'">'+ typeHtml.join('</div><div class="fw-flash-type-'+ type +'">') +'</div>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return html.join('');
|
||||
}
|
||||
},
|
||||
frontend: {
|
||||
/**
|
||||
* Html structure is the same as generated by FW_Flash_Messages::_print_frontend()
|
||||
* @param {Object} flashMessages
|
||||
* @returns {string}
|
||||
*/
|
||||
renderFlashMessages: function(flashMessages) {
|
||||
var html = [],
|
||||
typeHtml = [],
|
||||
messageClass = '';
|
||||
|
||||
jQuery.each(flashMessages, function(type, messages){
|
||||
typeHtml = [];
|
||||
|
||||
jQuery.each(messages, function(messageId, message){
|
||||
typeHtml.push('<li class="fw-flash-message">'+ message +'</li>');
|
||||
});
|
||||
|
||||
if (typeHtml.length) {
|
||||
html.push(
|
||||
'<ul class="fw-flash-type-'+ type +'">'+ typeHtml.join('</ul><ul class="fw-flash-type-'+ type +'">') +'</ul>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return html.join('');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Usage example
|
||||
if (false) {
|
||||
jQuery(function ($) {
|
||||
fwForm.initAjaxSubmit({
|
||||
selector: 'form[data-fw-form-id][data-fw-ext-forms-type="contact-forms"]',
|
||||
ajaxUrl: ajaxurl
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
/**
|
||||
* Basic options registry
|
||||
*/
|
||||
fw.options = (function ($, currentFwOptions) {
|
||||
/**
|
||||
* An object of hints
|
||||
*/
|
||||
var allOptionTypes = {};
|
||||
|
||||
currentFwOptions.get = get;
|
||||
currentFwOptions.getAll = getAll;
|
||||
currentFwOptions.register = register;
|
||||
currentFwOptions.__unsafePatch = __unsafePatch;
|
||||
currentFwOptions.getOptionDescriptor = getOptionDescriptor;
|
||||
currentFwOptions.startListeningToEvents = startListeningToEvents;
|
||||
currentFwOptions.getContextOptions = getContextOptions;
|
||||
currentFwOptions.findOptionInContextForPath = findOptionInContextForPath;
|
||||
currentFwOptions.findOptionInSameContextFor = findOptionInSameContextFor;
|
||||
|
||||
/**
|
||||
* fw.options.getValueForEl(element)
|
||||
* .then(function (values, optionDescriptor) {
|
||||
* // current values for option type
|
||||
* console.log(values)
|
||||
* })
|
||||
* .fail(function () {
|
||||
* // value extraction failed for some reason
|
||||
* });
|
||||
*/
|
||||
currentFwOptions.getValueForEl = getValueForEl;
|
||||
currentFwOptions.getContextValue = getContextValue;
|
||||
|
||||
return currentFwOptions;
|
||||
|
||||
/**
|
||||
* get hint object for a specific type
|
||||
*/
|
||||
function get (type) {
|
||||
return allOptionTypes[type] || allOptionTypes['fw-undefined'];
|
||||
}
|
||||
|
||||
function getAll () {
|
||||
return allOptionTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns:
|
||||
* el
|
||||
* ID
|
||||
* type
|
||||
* isRootOption
|
||||
* context
|
||||
* nonOptionContext
|
||||
*/
|
||||
function getOptionDescriptor (el) {
|
||||
var data = {};
|
||||
|
||||
if (! el) return null;
|
||||
|
||||
data.context = detectDOMContext(el);
|
||||
|
||||
data.el = findOptionDescriptorEl(el);
|
||||
|
||||
data.rootContext = findNonOptionContext(data.el);
|
||||
data.id = $(data.el).attr('data-fw-option-id');
|
||||
data.type = $(data.el).attr('data-fw-option-type');
|
||||
data.isRootOption = isRootOption(data.el, findNonOptionContext(data.el));
|
||||
data.hasNestedOptions = hasNestedOptions(data.el);
|
||||
|
||||
data.pathToTheTopContext = data.isRootOption
|
||||
? []
|
||||
: findPathToTheTopContext(data.el, findNonOptionContext(data.el));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function findOptionInSameContextFor (el, path) {
|
||||
var rootContext = getOptionDescriptor(el).rootContext;
|
||||
|
||||
return findOptionInContextForPath(
|
||||
rootContext, path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This receives a context (option as context works too)
|
||||
* and returns the option descriptor which respects the path
|
||||
*
|
||||
* - form
|
||||
* - .fw-backend-options-virtual-context
|
||||
* - .fw-backend-option-descriptor
|
||||
*
|
||||
* path:
|
||||
* id/other_id/another_one
|
||||
*/
|
||||
function findOptionInContextForPath (context, path) {
|
||||
var pathToTheTop = path.split('/');
|
||||
|
||||
return pathToTheTop.reduce(function (currentContext, path, index) {
|
||||
if (! currentContext) return false;
|
||||
|
||||
var elOrDescriptorForPath = _.compose(
|
||||
index === pathToTheTop.length - 1
|
||||
? getOptionDescriptor
|
||||
: _.identity,
|
||||
|
||||
_.partial(
|
||||
maybeFindFirstLevelOptionInContext,
|
||||
currentContext
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
return elOrDescriptorForPath(path);
|
||||
|
||||
}, context);
|
||||
|
||||
function maybeFindFirstLevelOptionInContext (context, firstLevelId) {
|
||||
return (getContextOptions(context).filter(
|
||||
function (optionDescriptor) {
|
||||
return optionDescriptor.id === firstLevelId;
|
||||
}
|
||||
)[0] || {}).el;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This receives a context (option as context works too)
|
||||
* and returns the first level of options underneath it.
|
||||
*
|
||||
* - form
|
||||
* - .fw-backend-options-virtual-context
|
||||
* - .fw-backend-option-descriptor
|
||||
*/
|
||||
function getContextOptions (el) {
|
||||
el = (el instanceof jQuery) ? el[0] : el;
|
||||
|
||||
if (! (
|
||||
el.tagName === 'FORM'
|
||||
||
|
||||
el.classList.contains('fw-backend-options-virtual-context')
|
||||
||
|
||||
el.classList.contains('fw-backend-option-descriptor')
|
||||
)) {
|
||||
throw "You passed an incorrect context element."
|
||||
}
|
||||
|
||||
return $(el)
|
||||
.find('.fw-backend-option-descriptor')
|
||||
.not(
|
||||
$(el).find('.fw-backend-options-virtual-context .fw-backend-option-descriptor')
|
||||
)
|
||||
.toArray()
|
||||
.map(getOptionDescriptor)
|
||||
.filter(function (descriptor) {
|
||||
return isRootOption(descriptor.el, el)
|
||||
})
|
||||
}
|
||||
|
||||
function getContextValue (el) {
|
||||
var optionDescriptors = getContextOptions(el);
|
||||
|
||||
var promise = $.Deferred();
|
||||
|
||||
fw.whenAll(optionDescriptors.map(getValueForOptionDescriptor))
|
||||
.then(function (valuesAsArray) {
|
||||
var values = {};
|
||||
|
||||
optionDescriptors.map(function (optionDescriptor, index) {
|
||||
values[optionDescriptor.id] = valuesAsArray[index].value;
|
||||
});
|
||||
|
||||
promise.resolve({
|
||||
valueAsArray: valuesAsArray,
|
||||
optionDescriptors: optionDescriptors,
|
||||
value: values
|
||||
});
|
||||
})
|
||||
.fail(function () {
|
||||
// TODO: pass a reason
|
||||
promise.reject();
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function getValueForOptionDescriptor (optionDescriptor) {
|
||||
var maybePromise = get(optionDescriptor.type).getValue(optionDescriptor)
|
||||
|
||||
var promise = maybePromise;
|
||||
|
||||
/**
|
||||
* A promise has a then method usually
|
||||
*/
|
||||
if (! promise.then) {
|
||||
promise = $.Deferred();
|
||||
promise.resolve(maybePromise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function getValueForEl (el) {
|
||||
return getValueForOptionDescriptor(getOptionDescriptor(el));
|
||||
}
|
||||
|
||||
/**
|
||||
* You are not registering here a full fledge class definition for an
|
||||
* option type just like we have on backend. It is more of a hint on how
|
||||
* to treat the option type on frontend. Everything should be working
|
||||
* almost fine even if you don't provide any hints.
|
||||
*
|
||||
* interface:
|
||||
*
|
||||
* startListeningForChanges
|
||||
* getValue
|
||||
*/
|
||||
function register (type, hintObject) {
|
||||
// TODO: maybe start triggering events on option type register
|
||||
|
||||
if (allOptionTypes[type]) {
|
||||
throw "Can't re-register an option type again";
|
||||
}
|
||||
|
||||
allOptionTypes[type] = jQuery.extend(
|
||||
{}, defaultHintObject(),
|
||||
hintObject || {}
|
||||
);
|
||||
}
|
||||
|
||||
function __unsafePatch (type, hintObject) {
|
||||
allOptionTypes[type] = jQuery.extend(
|
||||
{}, defaultHintObject(),
|
||||
(allOptionTypes[type] || {}),
|
||||
hintObject || {}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be automatically called at each fw:options:init event.
|
||||
* This will make each option type start listening to events
|
||||
*/
|
||||
function startListeningToEvents (el) {
|
||||
// TODO: compute path up untill non-option context
|
||||
el = (el instanceof jQuery) ? el[0] : el;
|
||||
|
||||
[].map.call(
|
||||
el.querySelectorAll('.fw-backend-option-descriptor[data-fw-option-type]'),
|
||||
function (el) {
|
||||
startListeningToEventsForSingle(getOptionDescriptor(el));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function startListeningToEventsForSingle (optionDescriptor) {
|
||||
get(optionDescriptor.type).startListeningForChanges(optionDescriptor)
|
||||
}
|
||||
|
||||
/**
|
||||
* We rely on the fact that by default, when we try to register some option
|
||||
* type -- the undefined and default one will be already registered.
|
||||
*/
|
||||
function defaultHintObject () {
|
||||
return get('fw-undefined') || {};
|
||||
}
|
||||
|
||||
function detectDOMContext (el) {
|
||||
el = findOptionDescriptorEl(el);
|
||||
|
||||
var nonOptionContext = findNonOptionContext(el);
|
||||
|
||||
return isRootOption(el, nonOptionContext)
|
||||
? nonOptionContext
|
||||
: findOptionDescriptorEl(el.parentElement);
|
||||
}
|
||||
|
||||
function findOptionDescriptorEl (el) {
|
||||
el = (el instanceof jQuery) ? el[0] : el;
|
||||
|
||||
if (! el) return false;
|
||||
|
||||
if (el.classList.contains('fw-backend-option-descriptor')) {
|
||||
return el;
|
||||
} else {
|
||||
var closestOption = $(el).closest(
|
||||
'.fw-backend-option-descriptor'
|
||||
);
|
||||
|
||||
if (closestOption.length === 0) {
|
||||
throw "There is no option descriptor for that element."
|
||||
}
|
||||
|
||||
return closestOption[0];
|
||||
}
|
||||
}
|
||||
|
||||
function isRootOption(el, nonOptionContext) {
|
||||
var parent;
|
||||
|
||||
// traverse parents
|
||||
while (el) {
|
||||
parent = el.parentElement;
|
||||
|
||||
if (parent === nonOptionContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parent && elementMatches(parent, '.fw-backend-option-descriptor')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
el = parent;
|
||||
}
|
||||
}
|
||||
|
||||
function findPathToTheTopContext (el, nonOptionContext) {
|
||||
var parent;
|
||||
|
||||
var result = [];
|
||||
|
||||
// traverse parents
|
||||
while (el) {
|
||||
parent = el.parentElement;
|
||||
|
||||
if (parent === nonOptionContext) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (parent && elementMatches(parent, '.fw-backend-option-descriptor')) {
|
||||
// result.push(parent.getAttribute('data-fw-option-type'));
|
||||
result.push(parent);
|
||||
}
|
||||
|
||||
el = parent;
|
||||
}
|
||||
|
||||
return result.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-option context has two possible values:
|
||||
*
|
||||
* - a form tag which encloses a list of root options
|
||||
* - a virtual context is an el with `.fw-backend-options-virtual-context`
|
||||
*/
|
||||
function findNonOptionContext (el) {
|
||||
var parent;
|
||||
|
||||
// traverse parents
|
||||
while (el) {
|
||||
parent = el.parentElement;
|
||||
|
||||
if (parent && elementMatches(parent, '.fw-backend-options-virtual-context, form')) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
el = parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasNestedOptions (el) {
|
||||
// exclude nested options within a virtual context
|
||||
|
||||
var optionDescriptor = findOptionDescriptorEl(el);
|
||||
|
||||
var hasVirtualContext = optionDescriptor.querySelector(
|
||||
'.fw-backend-options-virtual-context'
|
||||
);
|
||||
|
||||
if (! hasVirtualContext) {
|
||||
return !! optionDescriptor.querySelector(
|
||||
'.fw-backend-option-descriptor'
|
||||
);
|
||||
}
|
||||
|
||||
// check if we have options which are not in the virtual context
|
||||
return optionDescriptor.querySelectorAll(
|
||||
'.fw-backend-option-descriptor'
|
||||
).length > optionDescriptor.querySelectorAll(
|
||||
'.fw-backend-options-virtual-context .fw-backend-option-descriptor'
|
||||
).length;
|
||||
}
|
||||
|
||||
function elementMatches (element, selector) {
|
||||
var matchesFn;
|
||||
|
||||
// find vendor prefix
|
||||
[
|
||||
'matches','webkitMatchesSelector','mozMatchesSelector',
|
||||
'msMatchesSelector','oMatchesSelector'
|
||||
].some(function(fn) {
|
||||
if (typeof document.body[fn] === 'function') {
|
||||
matchesFn = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
|
||||
return element[matchesFn](selector);
|
||||
}
|
||||
})(jQuery, (fw.options || {}));
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
(function($) {
|
||||
var simpleInputs = [
|
||||
'text',
|
||||
'short-text',
|
||||
'hidden',
|
||||
'password',
|
||||
'textarea',
|
||||
'html',
|
||||
'html-fixed',
|
||||
'html-full',
|
||||
'select',
|
||||
'short-select',
|
||||
'gmap-key',
|
||||
'slider',
|
||||
'short-slider',
|
||||
];
|
||||
|
||||
simpleInputs.map(function(optionType) {
|
||||
fw.options.register(optionType, {
|
||||
getValue: getValueForSimpleInput,
|
||||
});
|
||||
});
|
||||
|
||||
function getValueForSimpleInput(optionDescriptor) {
|
||||
return {
|
||||
value: optionDescriptor.el.querySelector('input, textarea, select')
|
||||
.value,
|
||||
optionDescriptor: optionDescriptor,
|
||||
};
|
||||
}
|
||||
|
||||
fw.options.register('unique', {
|
||||
getValue: function(optionDescriptor) {
|
||||
var actualValue = optionDescriptor.el.querySelector(
|
||||
'input, textarea, select'
|
||||
).value;
|
||||
|
||||
return {
|
||||
value: !!actualValue.trim() ? actualValue : fw.randomMD5(),
|
||||
optionDescriptor: optionDescriptor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
fw.options.register('checkbox', {
|
||||
getValue: function(optionDescriptor) {
|
||||
return {
|
||||
value: optionDescriptor.el.querySelector(
|
||||
'input.fw-option-type-checkbox'
|
||||
).checked,
|
||||
optionDescriptor: optionDescriptor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
fw.options.register('checkboxes', {
|
||||
getValue: function(optionDescriptor) {
|
||||
var checkboxes = $(optionDescriptor.el)
|
||||
.find('[type="checkbox"]')
|
||||
.slice(1);
|
||||
|
||||
var value = {};
|
||||
|
||||
checkboxes.toArray().map(function(el) {
|
||||
value[$(el).attr('data-fw-checkbox-id')] = el.checked;
|
||||
});
|
||||
|
||||
return {
|
||||
value: value,
|
||||
optionDescriptor: optionDescriptor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
fw.options.register('radio', {
|
||||
getValue: function(optionDescriptor) {
|
||||
return {
|
||||
value: $(optionDescriptor.el).find('input:checked').val(),
|
||||
optionDescriptor: optionDescriptor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
fw.options.register('select-multiple', {
|
||||
getValue: function(optionDescriptor) {
|
||||
return {
|
||||
value: $(optionDescriptor.el.querySelector('select')).val(),
|
||||
optionDescriptor: optionDescriptor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
fw.options.register('multi', {
|
||||
getValue: function(optionDescriptor) {
|
||||
var promise = $.Deferred();
|
||||
|
||||
fw.options
|
||||
.getContextValue(optionDescriptor.el)
|
||||
.then(function(result) {
|
||||
promise.resolve({
|
||||
value: result.value,
|
||||
optionDescriptor: optionDescriptor,
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,287 @@
|
||||
(function ($) {
|
||||
|
||||
fw.options.register('fw-undefined', {
|
||||
startListeningForChanges: defaultStartListeningForChanges,
|
||||
getValue: defaultGetValue
|
||||
});
|
||||
|
||||
function defaultGetValue (optionDescriptor) {
|
||||
var resultPromise = $.Deferred();
|
||||
|
||||
/**
|
||||
* If we get a really undefined option type.
|
||||
*/
|
||||
if (optionDescriptor.type === 'fw-undefined') {
|
||||
resultPromise.resolve({
|
||||
value: '',
|
||||
optionDescriptor: optionDescriptor
|
||||
})
|
||||
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
// 1. find all inputs and ignore virtual contexts
|
||||
// this really should include nested options and properly serialize
|
||||
// them together
|
||||
//
|
||||
// we should serialize those inputs into an object, based on their
|
||||
// name attribute
|
||||
var formInstance = new FormSerializer($, optionDescriptor.el);
|
||||
|
||||
var inputValues = formInstance.addPairs(
|
||||
findInputsFromAContextAndIgnoreVirtualScopes(
|
||||
optionDescriptor.el
|
||||
).serializeArray()
|
||||
).serialize();
|
||||
|
||||
// 2. remove name_prefixes from those inputs
|
||||
// optionsDescriptor.id === 'laptop'
|
||||
// name="fw_options[nesting][laptop]"
|
||||
//
|
||||
// This step should get
|
||||
// inputValues['fw_options']['nesting']['laptop']
|
||||
|
||||
inputValues = inputValues[
|
||||
Object.keys(inputValues)[0]
|
||||
];
|
||||
|
||||
if (optionDescriptor.pathToTheTopContext.length > 0) {
|
||||
var IDs = optionDescriptor.pathToTheTopContext.map(
|
||||
fw.options.getOptionDescriptor
|
||||
);
|
||||
|
||||
IDs.map(function (localDescriptor) {
|
||||
inputValues = inputValues[localDescriptor.id];
|
||||
});
|
||||
}
|
||||
|
||||
var options = {};
|
||||
|
||||
options[optionDescriptor.id] = JSON.parse(jQuery(optionDescriptor.el).attr(
|
||||
'data-fw-for-js'
|
||||
)).option;
|
||||
|
||||
// 3. construct an AJAX request with correct options and input values
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
dataType: "json",
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
action: 'fw_backend_options_get_values',
|
||||
name_prefix: 'fw_options',
|
||||
options: [
|
||||
options
|
||||
],
|
||||
fw_options: inputValues
|
||||
}
|
||||
})
|
||||
.then(function (response, status, request) {
|
||||
if (response.success && request.status === 200) {
|
||||
resultPromise.resolve(
|
||||
{
|
||||
value: response.data.values[optionDescriptor.id],
|
||||
optionDescriptor: optionDescriptor
|
||||
}
|
||||
);
|
||||
} else {
|
||||
resultPromise.reject();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
// TODO: pass a reason
|
||||
resultPromise.reject();
|
||||
});
|
||||
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
// By default, for unknown option types do listening only once
|
||||
function defaultStartListeningForChanges (optionDescriptor) {
|
||||
if (optionDescriptor.el.classList.contains('fw-listening-started')) {
|
||||
return;
|
||||
}
|
||||
|
||||
optionDescriptor.el.classList.add('fw-listening-started');
|
||||
|
||||
listenToChangesForCurrentOptionAndPreserveScoping(
|
||||
optionDescriptor.el,
|
||||
_.throttle(function (e) {
|
||||
fw.options.trigger.changeForEl(e.target);
|
||||
}, 300)
|
||||
);
|
||||
|
||||
if (optionDescriptor.hasNestedOptions) {
|
||||
fw.options.on.changeByContext(optionDescriptor.el, function (nestedDescriptor) {
|
||||
fw.options.trigger.changeForEl(optionDescriptor.el);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* rewrite that with:
|
||||
*
|
||||
* - Array.filter
|
||||
* - Array.includes
|
||||
* - addEventListener
|
||||
* - querySelectorAll
|
||||
*/
|
||||
function listenToChangesForCurrentOptionAndPreserveScoping (el, callback) {
|
||||
jQuery(el).find(
|
||||
'input, select, textarea'
|
||||
).not(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-option-descriptor input'
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-option-descriptor select'
|
||||
)
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-option-descriptor textarea'
|
||||
)
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-options-virtual-context input'
|
||||
)
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-options-virtual-context select'
|
||||
)
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-options-virtual-context textarea'
|
||||
)
|
||||
)
|
||||
).on('change', callback);
|
||||
}
|
||||
|
||||
function findInputsFromAContextAndIgnoreVirtualScopes (el) {
|
||||
return jQuery(el).find(
|
||||
'input, select, textarea'
|
||||
).not(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-options-virtual-context input'
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-options-virtual-context select'
|
||||
)
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-backend-options-virtual-context textarea'
|
||||
)
|
||||
)
|
||||
).not(
|
||||
jQuery(el).find(
|
||||
'.fw-filter-from-serialization input'
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-filter-from-serialization select'
|
||||
)
|
||||
).add(
|
||||
jQuery(el).find(
|
||||
'.fw-filter-from-serialization textarea'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* USAGE:
|
||||
*
|
||||
* var formInstance = new FormSerializer(jQuery, document.body);
|
||||
*
|
||||
* formInstance.addPairs(jQuery('input').serializeArray());
|
||||
* formInstance.serialize();
|
||||
*/
|
||||
function FormSerializer(helper, $form) {
|
||||
var patterns = {
|
||||
push: /^$/,
|
||||
fixed: /^\d+$/,
|
||||
validate: /^[a-z][a-z0-9_-]*(?:\[(?:\d*|[a-z0-9_-]+)\])*$/i,
|
||||
key: /[a-z0-9_-]+|(?=\[\])/gi,
|
||||
named: /^[a-z0-9_-]+$/i
|
||||
};
|
||||
|
||||
// private variables
|
||||
var data = {},
|
||||
pushes = {};
|
||||
|
||||
// private API
|
||||
function build(base, key, value) {
|
||||
base[key] = value;
|
||||
return base;
|
||||
}
|
||||
|
||||
function makeObject(root, value) {
|
||||
var keys = root.match(patterns.key), k;
|
||||
|
||||
// nest, nest, ..., nest
|
||||
while ((k = keys.pop()) !== undefined) {
|
||||
// foo[]
|
||||
if (patterns.push.test(k)) {
|
||||
var idx = incrementPush(root.replace(/\[\]$/, ''));
|
||||
value = build([], idx, value);
|
||||
}
|
||||
|
||||
// foo[n]
|
||||
else if (patterns.fixed.test(k)) {
|
||||
value = build([], k, value);
|
||||
}
|
||||
|
||||
// foo; foo[bar]
|
||||
else if (patterns.named.test(k)) {
|
||||
value = build({}, k, value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function incrementPush(key) {
|
||||
if (pushes[key] === undefined) {
|
||||
pushes[key] = 0;
|
||||
}
|
||||
|
||||
return pushes[key]++;
|
||||
}
|
||||
|
||||
function encode(pair) {
|
||||
switch ($('[name="' + pair.name + '"]', $form).attr("type")) {
|
||||
case "checkbox":
|
||||
return pair.value === "on" ? true : pair.value;
|
||||
default:
|
||||
return pair.value;
|
||||
}
|
||||
}
|
||||
|
||||
function addPair(pair) {
|
||||
if (! patterns.validate.test(pair.name)) return this;
|
||||
|
||||
var obj = makeObject(pair.name, encode(pair));
|
||||
data = helper.extend(true, data, obj);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
function addPairs(pairs) {
|
||||
if (!helper.isArray(pairs)) {
|
||||
throw new Error("formSerializer.addPairs expects an Array");
|
||||
}
|
||||
for (var i=0, len=pairs.length; i<len; i++) {
|
||||
this.addPair(pairs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
function serialize() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// public API
|
||||
this.addPair = addPair;
|
||||
this.addPairs = addPairs;
|
||||
this.serialize = serialize;
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Basic mechanism that allows option types to notify the rest of the world
|
||||
* about the fact that they was changed by the user.
|
||||
*
|
||||
* Each option type is responsible for triggering such events and they should
|
||||
* at least try to supply a reasonable current value for it. Additional meta
|
||||
* data about the particular option type is infered automatically and can be
|
||||
* overrided by the option which triggers the event.
|
||||
*
|
||||
* In theory (and also in practice), options can and should trigger events
|
||||
* which are different than the `change`, because complicated option types
|
||||
* has many lifecycle events which everyone should be aware about and be able
|
||||
* to hook into. A lot of options already trigger such events but they do that
|
||||
* in an inconsistent manner which leads to poorly named and poorly namespaced
|
||||
* events.
|
||||
*
|
||||
* TODO: document this better
|
||||
*/
|
||||
fw.options = (function($, currentFwOptions) {
|
||||
currentFwOptions.on = on;
|
||||
currentFwOptions.off = off;
|
||||
currentFwOptions.trigger = trigger;
|
||||
|
||||
/**
|
||||
* Allows:
|
||||
* fw.options.trigger(...)
|
||||
* fw.options.trigger.change(...)
|
||||
* fw.options.trigger.forEl(...)
|
||||
* fw.options.trigger.changeForEl(...)
|
||||
* fw.options.trigger.scopedByType(...)
|
||||
*/
|
||||
currentFwOptions.trigger.change = triggerChange;
|
||||
currentFwOptions.trigger.forEl = triggerForEl;
|
||||
currentFwOptions.trigger.changeForEl = triggerChangeForEl;
|
||||
currentFwOptions.trigger.scopedByType = triggerScopedByType;
|
||||
|
||||
/**
|
||||
* Allows:
|
||||
* fw.options.on(...)
|
||||
* fw.options.on.one(...)
|
||||
* fw.options.on.change(...)
|
||||
* fw.options.on.changeByContext(...)
|
||||
*/
|
||||
currentFwOptions.on.one = one;
|
||||
currentFwOptions.on.change = onChange;
|
||||
currentFwOptions.on.changeByContext = onChangeByContext;
|
||||
|
||||
/**
|
||||
* Allows:
|
||||
* fw.options.off(...)
|
||||
* fw.options.off.change(...)
|
||||
*/
|
||||
currentFwOptions.off.change = offChange;
|
||||
|
||||
/**
|
||||
* A small little service for fetching HTML for option types from server.
|
||||
* It will perform caching for results inside a key-value store.
|
||||
*
|
||||
* Allows:
|
||||
*
|
||||
* fw.options.fetchHtml(options, values)
|
||||
* fw.options.fetchHtml.getCacheEntryFor(options, values)
|
||||
* fw.options.fetchHtml.emptyCache();
|
||||
*
|
||||
* // TODO: provide a way to empty cache for a specific set of options???
|
||||
*/
|
||||
var htmlCache = {};
|
||||
|
||||
fw.options.fetchHtml = fetchHtml;
|
||||
fw.options.fetchHtml.getCacheEntryFor = fetchHtmlGetCacheEntryFor;
|
||||
fw.options.fetchHtml.emptyCache = fetchHtmlEmptyCache;
|
||||
|
||||
/**
|
||||
* A helper for getting actual values for a set of options and values.
|
||||
* Much better than fw.getValuesFromServer() because it doesn't require
|
||||
* you to encode values as form data params. You just pass a valid JSON
|
||||
* object and it just works.
|
||||
*
|
||||
* fw.options
|
||||
* .getActualValues({a: {type: 'text', value: 'Initial'})
|
||||
* .then(function (values) {
|
||||
* // {
|
||||
* // a: 'Initial'
|
||||
* // }
|
||||
* console.log(values);
|
||||
* });
|
||||
*
|
||||
* fw.options
|
||||
* .getActualValues({a: {type: 'text', value: 'Initial'}, {a: 'Changed'})
|
||||
* .then(function (values) {
|
||||
* // {
|
||||
* // a: 'Changed'
|
||||
* // }
|
||||
* console.log(values);
|
||||
* });
|
||||
*/
|
||||
fw.options.getActualValues = getActualValues;
|
||||
|
||||
return currentFwOptions;
|
||||
|
||||
function onChange(listener) {
|
||||
on('change', listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Please note that you won't be able to off that listener easily because
|
||||
* it rewrites the listener which gets passed to fwEvents.
|
||||
*
|
||||
* If you want to be able to off the listener you should attach it with
|
||||
* onChange and filter based on context by yourself.
|
||||
*/
|
||||
function onChangeByContext(context, listener) {
|
||||
onChange(function(data) {
|
||||
if (data.context === findOptionDescriptorEl(context)) {
|
||||
listener(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function on(eventName, listener) {
|
||||
fwEvents.on('fw:options:' + eventName, listener);
|
||||
}
|
||||
|
||||
function one(eventName, listener) {
|
||||
fwEvents.one('fw:options:' + eventName, listener);
|
||||
}
|
||||
|
||||
function off(eventName, listener) {
|
||||
fwEvents.off('fw:options:' + eventName, listener);
|
||||
}
|
||||
|
||||
function offChange(listener) {
|
||||
off('change', listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* data:
|
||||
* optionId
|
||||
* type
|
||||
* value
|
||||
* context
|
||||
* el
|
||||
*/
|
||||
function trigger(eventName, data) {
|
||||
fwEvents.trigger('fw:options:' + eventName, data);
|
||||
}
|
||||
|
||||
function triggerForEl(eventName, el, data) {
|
||||
trigger(eventName, getActualData(el, data));
|
||||
}
|
||||
|
||||
function triggerChange(data) {
|
||||
trigger('change', data);
|
||||
}
|
||||
|
||||
function triggerChangeForEl(el, data) {
|
||||
triggerChange(getActualData(el, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a scoped event for a specific option type, has the form:
|
||||
* fw:options:{type}:{eventName}
|
||||
*/
|
||||
function triggerScopedByType(eventName, el, data) {
|
||||
data = getActualData(el, data);
|
||||
|
||||
trigger(data.type + ':' + eventName, data);
|
||||
}
|
||||
|
||||
function getActualData(el, data) {
|
||||
return $.extend({}, currentFwOptions.getOptionDescriptor(el), data);
|
||||
}
|
||||
|
||||
function findOptionDescriptorEl(el) {
|
||||
el = el instanceof jQuery ? el[0] : el;
|
||||
|
||||
return el.classList.contains('fw-backend-option-descriptor')
|
||||
? el
|
||||
: $(el).closest('.fw-backend-option-descriptor')[0];
|
||||
}
|
||||
|
||||
function fetchHtml(options, values, settings) {
|
||||
var promise = $.Deferred();
|
||||
|
||||
if (!settings) settings = {};
|
||||
|
||||
settings = _.extend({ name_prefix: 'fw_edit_options_modal' }, settings);
|
||||
|
||||
var cacheId = fetchHtmlGetCacheId(options, values);
|
||||
|
||||
if (typeof htmlCache[cacheId] !== 'undefined') {
|
||||
promise.resolve(htmlCache[cacheId]);
|
||||
return promise;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fw_backend_options_render',
|
||||
options: JSON.stringify(options),
|
||||
values: JSON.stringify(
|
||||
typeof values == 'undefined' ? {} : values
|
||||
),
|
||||
data: {
|
||||
name_prefix: settings.name_prefix,
|
||||
id_prefix: settings.name_prefix.replace(/_/g, '-') + '-',
|
||||
},
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response, status, xhr) {
|
||||
if (!response.success) {
|
||||
promise.reject('Error: ' + response.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
htmlCache[cacheId] = response.data.html;
|
||||
|
||||
promise.resolve(response.data.html, response, status, xhr);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
promise.reject(status + ': ' + String(error));
|
||||
},
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function fetchHtmlGetCacheEntryFor(options, values) {
|
||||
return htmlCache[fetchHtmlGetCacheId(options, values)];
|
||||
}
|
||||
|
||||
function fetchHtmlEmptyCache() {
|
||||
htmlCache = {};
|
||||
}
|
||||
|
||||
function fetchHtmlGetCacheId(options, values) {
|
||||
return fw.md5(
|
||||
JSON.stringify(options) +
|
||||
'~' +
|
||||
JSON.stringify(typeof values == 'undefined' ? {} : values)
|
||||
);
|
||||
}
|
||||
|
||||
function getActualValues (options, values) {
|
||||
var promise = $.Deferred();
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fw_backend_options_get_values_json',
|
||||
options: JSON.stringify(options),
|
||||
values: JSON.stringify(
|
||||
typeof values == 'undefined' ? {} : values
|
||||
)
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response, status, xhr) {
|
||||
if (!response.success) {
|
||||
promise.reject('Error: ' + response.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
promise.resolve(response.data.values, response, status, xhr);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
promise.reject(status + ': ' + String(error));
|
||||
},
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
})(jQuery, fw.options || {});
|
||||
2424
wp-content/plugins/unyson/framework/static/js/fw.js
Normal file
2424
wp-content/plugins/unyson/framework/static/js/fw.js
Normal file
File diff suppressed because it is too large
Load Diff
34
wp-content/plugins/unyson/framework/static/js/ie-fixes.js
Normal file
34
wp-content/plugins/unyson/framework/static/js/ie-fixes.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* IE fixes for jQuery plugins to not throw errors
|
||||
*/
|
||||
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function (searchElement, fromIndex) {
|
||||
if ( this === undefined || this === null ) {
|
||||
throw new TypeError( '"this" is null or not defined' );
|
||||
}
|
||||
|
||||
var length = this.length >>> 0; // Hack to convert object.length to a UInt32
|
||||
|
||||
fromIndex = +fromIndex || 0;
|
||||
|
||||
if (Math.abs(fromIndex) === Infinity) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
|
||||
if (fromIndex < 0) {
|
||||
fromIndex += length;
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (;fromIndex < length; fromIndex++) {
|
||||
if (this[fromIndex] === searchElement) {
|
||||
return fromIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
jQuery( document ).ready( function ( $ ) {
|
||||
setTimeout( function () {
|
||||
fwEvents.trigger( 'fw:options:init', {
|
||||
$elements: $( document.body )
|
||||
} );
|
||||
}, 30 );
|
||||
|
||||
function updateContent( $content ) {
|
||||
if ( tinymce.get( 'content' ) ) {
|
||||
tinymce.get( 'content' ).setContent( $content );
|
||||
} else {
|
||||
$content.val( $content );
|
||||
}
|
||||
}
|
||||
|
||||
$( '#post-preview' ).on( 'mousedown touchend', function () {
|
||||
|
||||
var $content = $( '#content' ),
|
||||
$contentValue = tinymce.get( 'content' ) ? tinymce.get( 'content' ).getContent() : $content.val(),
|
||||
$session = '<!-- <fw_preview_session>' + new Date().getTime() + '</fw_preview_session> -->';
|
||||
|
||||
if ( $contentValue.indexOf( '<!-- <fw_preview_session>' ) !== -1 ) {
|
||||
$contentValue = $contentValue.replace( /<!-- <fw_preview_session>(.*?)<\/fw_preview_session> -->/gi, $session );
|
||||
} else {
|
||||
$contentValue = $contentValue + $session;
|
||||
}
|
||||
|
||||
updateContent( $contentValue );
|
||||
updateContent( $contentValue.replace( /<!-- <fw_preview_session>(.*?)<\/fw_preview_session> -->/gi, '' ) );
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user