Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
#tvd-contents-sets h4{margin:10px 0;font-size:1.1em}#tvd-content-sets-autocomplete{width:400px;max-width:100%;height:36px;outline:none;padding:0 10px;border-radius:4px;border:solid 1px rgba(176,185,193,.8);position:relative;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%23b0b9c1' d='M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z'/%3E%3C/svg%3E") center right 10px/15px no-repeat}#tvd-content-sets-autocomplete::placeholder{color:#b0b9c1}.tvd-content-sets-dropdown{box-shadow:0 1px 3px 0 rgba(0,0,0,.22);background-color:#fff;border:none;border-bottom:1px solid #a3a3a3}.tvd-content-sets-dropdown li{line-height:1.5rem;min-height:22px;margin-bottom:0;padding:6px;font-size:14px}.tvd-content-sets-dropdown li:hover{background-color:#f6f6f6}#tvd-matched-content-sets:not(:empty){background-color:rgba(241,241,241,.5);margin:10px 0 0;font-size:14px;padding:10px 10px 5px;width:400px;max-width:100%;box-sizing:border-box}#tvd-matched-content-sets:not(:empty)>div{background-color:#fff;line-height:22px;border:solid 1px #c0cad1;border-radius:2px;color:#50565f;position:relative;display:inline-flex;align-items:center;margin:0 5px 5px 0;padding:0 5px}.removeMatchedTag{color:#c0cad1;font-weight:normal;margin-left:10px;cursor:pointer}.removeMatchedTag:after{content:"×"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
module.exports = Backbone.Collection.extend( {
model: require( '../models/rule' ),
isCompleted() {
let isComplete = true;
if ( this.length === 0 ) {
isComplete = false
} else {
this.each( model => {
if ( ! model.isCompleted() ) {
isComplete = false;
}
} );
}
return isComplete
}
} );

View File

@@ -0,0 +1,118 @@
module.exports = Backbone.Collection.extend( {
model: require( '../models/set' ),
/**
* @property {number} offset offset
*/
offset: 0,
/**
* @property {number} of items per page
*/
limit: 8,
/**
* @property {number} of current page
*/
currentPage: 1,
/**
* Filter - map of key => value pairs to be sent to the server for filtering
*/
_filter: {},
/**
* Checks if the collection has filters
*
* @return {boolean} has filters
*/
hasFilters() {
return Object.keys( this._filter ).length > 0;
},
/**
* Reset all filters
*
* @return {Backbone.Collection} this
*/
resetFilters() {
this.currentPage = 1;
this.offset = 0;
this._filter = {};
return this;
},
applyFilters( key, value ) {
this.currentPage = 1;
this.offset = 0;
this._filter[ key ] = value;
return this;
},
/**
* Increment the current page and calculates the new offset
*
* @return {Backbone.Collection} this
*/
next() {
this.currentPage ++;
this.offset = ( this.currentPage - 1 ) * this.limit;
return this;
},
/**
* Decrement the current page and calculates the new offset
*
* @return {Backbone.Collection} this
*/
prev() {
this.currentPage --;
this.offset = ( this.currentPage - 1 ) * this.limit;
return this;
},
/**
* Based on current items returns them to be used outside:
* - used on pagination template
*
* @return {{next: boolean, prev: boolean, totalPages: number, page: *, currentPage: *}} page information
*/
pageInfo() {
const total = this.length;
return {
currentPage: this.currentPage,
totalPages: Math.ceil( total / this.limit ),
page: this.currentPage,
prev: this.offset > 0,
next: ( this.currentPage * this.limit ) < total,
}
},
paginated() {
const page = this.currentPage - 1,
removeIds = [];
let collection = this.clone();
if ( this.hasFilters() ) {
if ( this._filter.search && this._filter.search.length > 0 ) {
collection.each( model => {
const title = model.get( 'post_title' ).toLowerCase();
if ( title.indexOf( this._filter.search ) === - 1 ) {
removeIds.push( model );
}
} );
for ( let i in removeIds ) {
collection.remove( removeIds[ i ] );
}
}
}
collection = _( collection.rest( this.limit * page ) );
collection = _( collection.first( this.limit ) );
return collection;
}
} );

View File

@@ -0,0 +1,118 @@
( function ( $ ) {
/**
* On document ready init app
*/
$( tvdInitApp );
/**
* Init App
*/
function tvdInitApp() {
TD.matches = new Backbone.Collection( TD.matches || [] );
TD.sets = new Backbone.Collection( TD.sets || [] );
new AppView( {
el: $( '#tvd-contents-sets' ),
} );
}
/**
* Main App View
*/
const AppView = Backbone.View.extend( {
events: {
/**
* @param {Event} event
*/
'click .removeMatchedTag': event => {
const ID = parseInt( event.currentTarget.parentNode.getAttribute( 'data-id' ) ),
matchedModel = TD.matches.findWhere( {id: ID} );
if ( matchedModel ) {
TD.matches.remove( matchedModel );
}
},
},
/**
* @param {Object} options
*/
initialize( options ) {
$.extend( this, true, options );
this.listenTo( TD.matches, 'add remove', this.renderMatchedTags );
this.$autocomplete = this.$( '#tvd-content-sets-autocomplete' );
this.$matchedWrapper = this.$( '#tvd-matched-content-sets' );
this.renderMatchedTags();
},
renderMatchedTags() {
this.$matchedWrapper.empty();
/**
* Array needed to send data to backend
*
* @type {string[]}
*/
const matchedIDs = [];
TD.matches.each( matchedModel => {
this.$matchedWrapper.append( `<div data-id="${matchedModel.get( 'id' )}"><span>${matchedModel.get( 'text' )}</span><span class="removeMatchedTag"></span></div>` )
matchedIDs.push( matchedModel.get( 'id' ) );
} );
this.bindAutocomplete();
if ( matchedIDs.length ) {
this.$matchedWrapper.append( `<input type="hidden" name="tvd_matched_content_sets_id" value="${matchedIDs.toString()}" />` );
}
},
_escape( value ) {
return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
},
bindAutocomplete() {
if ( this.$autocomplete.data( 'autocomplete' ) ) {
this.$autocomplete.autocomplete( 'destroy' );
this.$autocomplete.removeData( 'autocomplete' );
}
this.$autocomplete.autocomplete( {
appendTo: this.$autocomplete.parent(),
minLength: 0,
classes: {
'ui-autocomplete': 'tvd-content-sets-dropdown',
},
source: ( request, response ) => {
const regex = new RegExp( this._escape( request.term ), 'i' ),
setsJSON = TD.sets.toJSON().filter( elem => typeof TD.matches.findWhere( {id: elem.id} ) === 'undefined' ),
recs = request.term.trim().length === 0 ? setsJSON : $.grep( setsJSON, function ( set ) {
return regex.test( set.text )
} );
response( $.map( recs, function ( set ) {
return {
label: set.text,
key: set.id
};
} ) );
},
select: ( event, ui ) => {
this.$autocomplete.val( '' );
const matchedModel = TD.sets.findWhere( {id: ui.item.key} );
if ( matchedModel ) {
TD.matches.add( matchedModel.clone() );
}
return false;
}
} )
}
} );
} )( jQuery );

View File

@@ -0,0 +1,40 @@
( function ( $ ) {
/**
* We need this because this needs to be implemented in other plugins as well
*
* Ex: Thrive Apprentice
*/
$.extend( TD_SETS, {
Models: {
Set: require( './models/set' ),
Rule: require( './models/rule' ),
},
Collections: {
Sets: require( './collection/sets' ),
Rules: require( './collection/rules' ),
},
Views: {
List: require( './views/list' ),
Item: require( './views/item' ),
Rule: require( './views/rule' ),
Form: require( './views/form' ),
Pagination: require( './views/pagination' ),
Confirm: require( './views/confirm-action' ),
Modals: {
Edit: require( './views/modals/edit' ),
},
Controls: {
Base: require('./views/controls/base')
}
},
} );
/**
* Allow other functionality to modify stuff on TD_SETS constant
*
* Used in TA Plugin
*/
$( window ).trigger( 'td_sets_ready', TD_SETS );
TD_SETS.sets = new TD_SETS.Collections.Sets( TD_SETS.sets );
} )( jQuery );

View File

@@ -0,0 +1,65 @@
module.exports = Backbone.Model.extend( {
/**
* Append WP Nonce to all requests
*
* @param {string} method
* @param {*} collection
* @param {*} options
*/
sync( method, collection, options ) {
const beforeSend = options.beforeSend;
options.beforeSend = function ( xhr ) {
xhr.setRequestHeader( 'X-WP-Nonce', TD_SETS.nonce );
if ( typeof beforeSend === 'function' ) {
beforeSend.apply( this, arguments );
}
};
return Backbone.Model.prototype.sync.apply( this, arguments );
},
/**
* Builds an correctly-formatted URL from baseUri (which can already contain a query string) and (optionally) a map of query string parameters
*
* @param {string} baseUri
* @param {Object} [params]
* @param {string} [pathAppend] string to be appended to the path of the url object
*
* @return {string} url
*/
buildUrl( baseUri, params = {}, pathAppend = '' ) {
const url = new URL( baseUri );
_.each( params, ( value, key ) => {
url.searchParams.append( key, value );
} );
if ( pathAppend ) {
if ( baseUri.includes( '?rest_route' ) ) { //Fixed issue with different type of permalinks
url.searchParams.set( 'rest_route', url.searchParams.get( 'rest_route' ) + pathAppend );
} else {
url.pathname += pathAppend;
}
}
return url.toString();
},
/**
* Wraps up an error object for later use
*
* @param {string} field
* @param {string} message
* @return {{field: *, message: *}} error object
*/
validation_error( field, message ) {
return {
field,
message
};
},
/**
* Gets the first error message from list if any defined
*
* @return {string} the error message
*/
getValidationError() {
return this.validationError && this.validationError[ 0 ] ? this.validationError[ 0 ].message : '';
}
} );

View File

@@ -0,0 +1,92 @@
module.exports = require( './base' ).extend( {
idAttribute: 'ID',
defaults() {
return {
content_type: '', //post or course
content: '',
field: '',
operator: '',
value: [],
content_label: {
singular: '',
plural: '',
}
}
},
orderSteps: [ 'content', 'field', 'operator', 'value' ],
getSteps() {
return this.orderSteps;
},
getValues() {
let _return = [];
if ( Array.isArray( this.get( 'value' ) ) ) {
this.get( 'value' ).forEach( val => {
if ( typeof val === 'object' && typeof val.id !== 'undefined' ) {
_return.push( val.id );
}
} );
}
return _return;
},
/**
* Checks if all the attributes of the model are filled
*
* @return {boolean}
*/
isCompleted() {
let isCompleted = true
this.orderSteps.some( step => {
if ( this.get( 'field' ) && parseInt( this.get( 'field' ) ) === - 1 ) {
/**
* if field === -1 return true.
*/
return true;
}
if ( this.get( step ).length === 0 ) {
isCompleted = false;
return true;
}
} );
return isCompleted;
},
/**
* Checks if all the attributes of the model are empty
*
* @return {boolean}
*/
isEmpty() {
let empty = true
this.orderSteps.some( step => {
if ( this.get( step ).length > 0 ) {
empty = false;
return true;
}
} );
return empty;
},
/**
* Updates the rule info from DB.
* mainly this is for content_label rule key
*
* @returns {Promise<unknown>}
*/
normalize() {
return new Promise( resolve => {
wp.apiRequest(
{
type: 'POST',
url: `${TD_SETS.routes.base}/normalize-rule`,
data: this.toJSON(),
}
).always( data => resolve( data ) );
} );
}
} );

View File

@@ -0,0 +1,64 @@
const RulesCollection = require( '../collection/rules' );
module.exports = require( './base' ).extend( {
idAttribute: 'ID',
allowEmptyRules: false,
defaults() {
return {
post_title: '',
post_content: new RulesCollection(),
}
},
initialize( options = {} ) {
if ( options.post_content instanceof Backbone.Collection ) {
options.post_content = jQuery.extend( true, [], options.post_content.toJSON() );
}
if ( options.post_content ) {
this.set( 'post_content', new RulesCollection( options.post_content ) );
}
},
parse( data ) {
if ( data.post_content instanceof Backbone.Collection ) {
data.post_content = jQuery.extend( true, [], data.post_content.toJSON() );
}
if ( data.post_content ) {
/**
* This is not optimal but gets the job done
*/
this.get( 'post_content' ).reset( data.post_content );
data = [];
}
return data;
},
url( path = '', params = {} ) {
return this.buildUrl( [
TD_SETS.routes.base,
this.get( 'ID' ),
path
].filter( i => i ).join( '/' ), params );
},
getRules() {
return this.get( 'post_content' );
},
/**
* Validates current model
*
* @param {Object} data
* @return {undefined|Array} result
*/
validate( data = {} ) {
const errors = [];
if ( ! data.post_title ) {
errors.push( this.validation_error( 'post_title', 'Title is empty!' ) );
}
if ( this.allowEmptyRules === false && ! data.post_content.isCompleted() ) {
errors.push( this.validation_error( 'post_content', 'You must add some content' ) );
}
if ( errors.length ) {
return errors;
}
},
} );

View File

@@ -0,0 +1,100 @@
( function ( $ ) {
module.exports = Backbone.View.extend( {
events: {
'click .click': '_call',
'change .change': '_call',
'input .input': '_call',
},
/**
* View Constructor
*
* @param {Object} options
*/
initialize( options ) {
$.extend( true, this, options );
if ( options && options.template ) {
this.template = options.template;
}
this.afterInitialize( options );
},
/**
* Call method for specific events
*
* @param {Event} event
* @return {*} result from handler function
*/
_call( event ) {
const _method = event.currentTarget.dataset.fn;
if ( typeof this[ _method ] === 'function' ) {
return this[ _method ].call( this, event, event.currentTarget );
}
},
/**
* Appends the template's html into $el
*
* @return {Backbone.View} the caller view
*/
render() {
if ( typeof this.template === 'string' ) {
this.template = TVE_Dash.tpl( this.template );
}
if ( typeof this.template === 'function' ) {
this.$el.html( this.template( {model: this.model} ) );
}
/**
* Used to do stuff after the template is applied.
*
* Ex: declare the view variables
*/
this.afterRender();
return this;
},
/**
* Opens a new modal
*
* @param {Function} modalView
* @param {Object} params
*/
openModal( modalView, params = {} ) {
if ( modalView.prototype instanceof TVE_Dash.views.Modal ) {
params =
{
...{
'max-width': 'calc(100% - 40px)',
width: '850px',
in_duration: 200,
out_duration: 300,
className: 'tvd-modal tva-modal-create',
dismissible: true
}, ...params
};
return TVE_Dash.modal( modalView, params );
}
// eslint-disable-next-line no-console
console.warn( 'Invalid type of modal view' )
},
/**
* Completely destroy the view and un-delegate any events
*/
destroy() {
this.stopListening();
this.undelegateEvents();
this.$el.removeData().off();
return this;
},
/**
* Overridden in child views
*/
afterRender: $.noop,
afterInitialize: $.noop,
} );
} )( jQuery );

View File

@@ -0,0 +1,16 @@
module.exports = require( './base' ).extend( {
/**
* Removes the view from DO because user doesn't confirm his action
* - defined in HTML
*/
cancel() {
this.remove();
},
/**
* This should be overwritten or extended when this view is initialized
* - defined in HTML
*/
confirm() {
this.remove();
}
} );

View File

@@ -0,0 +1,42 @@
module.exports = require( '../base' ).extend( {
template: '',
className: 'tvd-content-set-rule-step',
afterInitialize( options ) {
this.ruleModel = options.ruleModel;
this.step = options.step;
},
/**
* Checks if the step is completed
*
* @return {boolean}
*/
hasStoredValue() {
return this.getStoredValue().length > 0;
},
getStoredValue() {
return this.ruleModel.get( this.step );
},
/**
* Should be extended in other classes
* @param value
* @return {boolean}
*/
isValid( value ) {
return value && value.trim().length > 0;
},
change( event, dom ) {
if ( this.isValid( dom.value ) ) {
this.ruleModel.trigger( 'control-changed', this.step, this.processValue( dom.value ) );
}
},
/**
* Process the value before storing into the model
*
* @param {string|Array} value
*
* @return {string|Array}
*/
processValue( value ) {
return value;
}
} );

View File

@@ -0,0 +1,33 @@
module.exports = require( './base' ).extend( {
template: 'tvd-c-s-date-picker',
afterRender() {
this.$input = this.$( '.tvd-date-picker' );
this.$input.pickadate( {
firstDay: 1,
format: 'dd-mmm-yyyy',
formatSubmit: 'yyyy-mm-dd',
hiddenName: true,
} );
this.picker = this.$input.pickadate( 'picker' );
if ( this.hasStoredValue() ) {
this.$input.val( this.processFormat() );
}
},
processFormat() {
const date = new Date( this.getStoredValue() ),
mm = parseInt( date.getMonth() ) + 1,
yr = date.getFullYear(),
month = mm < 10 ? '0' + mm : mm,
day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
return day + '-' + month + '-' + yr;
},
processValue( value ) {
value = this.picker.get( 'select', 'yyyy-mm-dd' );
return value;
}
} );

View File

@@ -0,0 +1,80 @@
module.exports = require( './base' ).extend( {
template: 'tvd-c-s-select-multiple',
afterRender() {
this.$select = this.$( 'select' );
if ( this.$select.data( 'select2' ) ) {
this.$select.select2( 'destroy' );
}
const model = this.ruleModel;
this.$select.select2( {
placeholder: "Search for content",
minimumInputLength: 2,
delay: 250,
multiple: true,
tags: false, //TAGS: true or false. If set to false, new values can not be created
ajax: {
url: TD_SETS.routes.base,
type: 'GET',
dataType: 'json',
data: function ( obj ) {
return {...model.toJSON(), ...{_wpnonce: TD_SETS.nonce, query_string: obj.term}};
},
delay: 250,
cache: true,
processResults: function ( data ) {
return {
results: data
};
}
}
} );
if ( this.hasStoredValue() ) {
this.setSelected( this.getStoredValue() )
}
this.$select.data( 'select2' ).$container.addClass( 'tvd-content-set-select-multiple' );
this.$select.on( 'select2:select', ( e ) => {
this.change();
} ).on( 'select2:unselect', ( e ) => {
if ( ! e.params.originalEvent ) {
return;
}
e.params.originalEvent.stopPropagation();
this.change()
} ).data( 'select2' ).$dropdown.addClass( 'tvd-content-set-delete-select-multiple-dropdown' );
},
/**
* Sets value to a select that has data from remote location (dynamically from server)
*/
setSelected: function ( values ) {
const _ids = [];
this.$select.empty();
//Remove empty values
values = values.filter( ( a ) => a );
values.forEach( _val => {
_ids.push( _val.id );
this.$select.append( '<option value="' + _val.id + '">' + _val.text + '</option>' );
} );
this.$select.val( _ids ).trigger( 'change' );
},
change( event, dom ) {
const value = [];
this.$select.select2( 'data' ).forEach( data => {
value.push( {id: data.id, text: data.text} );
} );
this.ruleModel.trigger( 'control-changed', this.step, value );
}
} );

View File

@@ -0,0 +1,60 @@
module.exports = require( './base' ).extend( {
template: 'tvd-c-s-select',
afterRender() {
this.$select = this.$( 'select' );
this.model.get( 'options' ).forEach( option => {
const opt = `<option value="${option.value}"${option.disabled ? ' disabled selected' : ''}>${option.label}</option>`;
this.$select.append( opt );
} );
if ( this.hasStoredValue() ) {
this.setSelected( this.getStoredValue() )
}
if ( this.model.get( 'trigger_change' ) ) {
this.setSelected( this.getFirstOptionValue() );
setTimeout( () => {
this.triggerChange();
} )
}
this.$select.select2( {
//Disable the search input for simple select
minimumResultsForSearch: - 1,
width: '100%'
} );
this.$select.data( 'select2' ).$container.addClass( 'tvd-content-set-select-simple' );
this.$select.data( 'select2' ).$dropdown.addClass( 'tvd-content-set-select-dropdown' );
},
setSelected( selected ) {
if ( selected ) {
this.$select.find( `option[value="${selected}"]` ).attr( 'selected', 'selected' );
}
},
/**
* Returns the first non empty value
*
* @return {*}
*/
getFirstOptionValue() {
return this.$select.find( `option:eq(1)` ).val();
},
/**
* Triggers the change on the control element
*/
triggerChange() {
this.$select.trigger( 'change' );
},
/**
* A <select>ed value is always valid
*
* @return {boolean}
*/
isValid() {
return true;
}
} );

View File

@@ -0,0 +1,46 @@
module.exports = require( './base' ).extend( {
template: 'tvd-c-s-within-the-last',
afterRender() {
this.$input = this.$( 'input' );
this.$select = this.$( 'select' );
if ( this.hasStoredValue() ) {
const value = this.getStoredValue().split( ' ' );
this.$input.val( value[ 0 ].trim() );
this.$select.val( value[ 1 ].trim() );
} else {
this.$input.val( 0 );
this.$select.val( this.$select.find( 'option' ).first().attr( 'value' ) );
}
this.$select.select2();
this.$select.data( 'select2' ).$container.addClass( 'mt-10 tvd-content-set-select-simple' );
this.$select.data( 'select2' ).$dropdown.addClass( 'tvd-content-set-select-dropdown' );
},
isValid() {
const inputValue = this.$input.val();
if ( isNaN( inputValue ) || parseInt( inputValue ) < 0 ) {
this.$input.val( 1 );
return false;
}
return Number.isInteger( parseInt( this.$input.val() ) ) && this.$select.val().length > 0;
},
change( event, dom ) {
if ( this.isValid( dom.value ) ) {
const obj = {};
obj[ this.step ] = this.processValue( dom.value );
this.ruleModel.set( obj );
}
},
processValue( value ) {
value = `${this.$input.val()} ${this.$select.val()}`;
return value;
}
} );

View File

@@ -0,0 +1,135 @@
module.exports = require( './base' ).extend( {
template: 'form',
addNewRuleTemplate: 'add-new-rule',
addRuleView: null,
afterInitialize() {
this.listenTo( this.model.getRules(), 'add remove change', _.bind( this.rulesChanged, this ) );
this.listenTo( this.model.getRules(), 'add change', this.animateToBottom );
},
afterRender() {
this.$name = this.$( '#tvd-content-set-name' );
this.$rulesWrapper = this.$( '#tvd-content-set-rules' );
this.$addRuleWrapper = this.$( '#tvd-content-set-add-rule' );
this.$inner = this.$( '.tvd-content-set-inner' );
this.$saveButton = this.$( 'button' );
this.renderRules();
this.rulesChanged();
this.$name.on( 'input', () => {
if ( this.getTitle().length >= 1 ) {
this.$name.removeClass( 'tva-cset-error' );
}
} );
},
renderRules() {
this.$rulesWrapper.empty();
this.model.getRules().each( this.renderRule, this );
},
renderRule( ruleModel ) {
this.$rulesWrapper.append( ( new TD_SETS.Views.Rule( {
model: ruleModel,
collection: this.model.getRules(),
} ).render().$el ) )
},
rulesChanged() {
this.$addRuleWrapper.children().remove();
if ( this.allowAddNewRule() ) {
this.$addRuleWrapper.append( TVE_Dash.tpl( this.addNewRuleTemplate )() );
}
},
animateToBottom: function () {
this.$inner.animate( {
scrollTop: this.$inner[ 0 ].scrollTop + 1000
}, 150 );
},
addNewRule: function () {
const model = new TD_SETS.Models.Rule( {} );
this.model.getRules().add( model );
this.renderRule( model );
},
allowAddNewRule() {
let allow = true;
this.model.getRules().each( ruleModel => {
if ( ! ruleModel.isCompleted() ) {
allow = false;
}
} );
return allow;
},
getTitle() {
return this.$name.val().trim();
},
saveContentSet() {
const title = this.getTitle();
this.$( '.tva-cset-error' ).removeClass( 'tva-cset-error' );
this.$name.toggleClass( 'tva-cset-error', title.length === 0 );
if ( title.length === 0 ) {
setTimeout( () => {
this.$inner.animate( {scrollTop: 0}, 150 );
}, 200 );
TVE_Dash.err( 'Please add a name' );
return;
}
this.model.set( {'post_title': title}, {silent: true} );
if ( ! this.model.isValid() ) {
const changed = this.model.changedAttributes();
if ( changed && changed.post_title ) {
const previousAttributes = this.model.previousAttributes();
this.model.set( {'post_title': previousAttributes.post_title}, {silent: true} );
}
TVE_Dash.err( this.model.getValidationError() );
/**
* UI Validation:
*
* Highlight the empty fields
*/
this.$( 'input, select' ).each( ( index, elem ) => {
if ( elem.tagName.toLowerCase() === 'input' && elem.classList.contains( 'select2-search__field' ) ) {
const $select2 = jQuery( elem ).closest( '.tvd-content-set-rule-step' ).find( 'select' );
if ( $select2.val().length === 0 ) {
elem.classList.add( 'tva-cset-error' );
}
} else if ( ! elem.value ) {
elem.classList.add( 'tva-cset-error' );
}
} );
return;
}
this.$saveButton.addClass( 'disabled' );
TVE_Dash.showLoader();
this.model.save()
.done( response => {
this.collection.reset( response );
this.$saveButton.removeClass( 'disabled' );
TVE_Dash.hideLoader();
} )
.fail( r => {
if ( r.responseJSON && r.responseJSON.message ) {
TVE_Dash.err( 'Something went wrong: ' + r.responseJSON.message )
} else {
TVE_Dash.err( 'Something went wrong: ' + r.responseText )
}
this.$saveButton.removeClass( 'disabled' );
TVE_Dash.hideLoader();
} );
}
} );

View File

@@ -0,0 +1,43 @@
module.exports = require( './base' ).extend( {
className: 'tvd-content-set-item',
template: 'item',
/**
* Edit a content set callback
*
* Called from the UI
*/
edit: function () {
this.openModal( TD_SETS.Views.Modals.Edit, {
model: this.model,
collection: this.collection,
width: '810px',
className: 'tvd-modal tvd-content-set-edit'
} );
},
/**
* Deletes a content set callback
*
* Called from the UI
*/
delete: function () {
const confirmView = new TD_SETS.Views.Confirm( {
template: TVE_Dash.tpl( 'item-delete-confirmation' ),
className: 'tvd-content-set-delete-confirmation',
confirm: () => {
this.model.destroy( {
wait: true,
success: ( model, response ) => {
this.destroy().remove();
}
} );
},
cancel() {
this.remove();
}
} );
this.$el.append( confirmView.render().$el );
}
} );

View File

@@ -0,0 +1,80 @@
module.exports = require( './base' ).extend( {
template: 'list',
noItemsTemplate: 'tva-c-s-no-items',
noSearchItemsTemplate: 'tva-c-s-no-search-items',
pagination: null,
itemViews: {},
afterInitialize() {
this.listenTo( this.collection, 'reset', _.bind( this.renderList, this ) );
this.listenTo( this.collection, 'page-changed', _.bind( this.renderList, this ) );
this.listenTo( this.collection, 'remove', _.bind( () => {
const pageInfo = this.collection.pageInfo();
if ( pageInfo.currentPage > 1 && this.collection.paginated().toJSON().length === 0 ) {
this.collection.prev();
}
this.renderList();
this.pagination.render();
}, this ) );
},
afterRender() {
this.$list = this.$( '.tvd-content-sets-list' );
this.$pagination = this.$( '.tvd-content-sets-list-pagination' );
this.renderList();
this.renderPagination();
},
renderPagination() {
this.pagination = new TD_SETS.Views.Pagination( {
el: this.$pagination,
collection: this.collection,
} );
this.pagination.render();
},
filterSets( event, dom ) {
if ( dom.value.length ) {
this.collection.applyFilters( 'search', dom.value );
} else {
this.collection.resetFilters();
}
this.renderList();
this.pagination.toggle( ! this.collection.hasFilters() ).render();
},
renderList() {
this.$list.empty();
this.itemViews = {};
if ( this.collection.length === 0 ) {
return this.$list.html( TVE_Dash.tpl( this.noItemsTemplate ) );
}
const paginatedCollection = this.collection.paginated();
if ( this.collection.hasFilters() && paginatedCollection.size() === 0 ) {
return this.$list.html( TVE_Dash.tpl( this.noSearchItemsTemplate ) );
}
paginatedCollection.forEach( this.renderItem, this );
},
renderItem( model ) {
const view = new TD_SETS.Views.Item( {
model: model,
collection: this.collection,
} );
this.$list.append( view.render().$el );
this.itemViews[ model.get( 'ID' ) ] = view;
},
addSet() {
this.openModal( TD_SETS.Views.Modals.Edit, {
model: new TD_SETS.Models.Set( {} ),
collection: this.collection,
width: '810px',
className: 'tvd-modal tvd-content-set-edit'
} );
}
} );

View File

@@ -0,0 +1,35 @@
( function ( $ ) {
module.exports = TVE_Dash.views.Modal.extend( {
template: '',
events: {
'click .click': '_call',
'input .input': '_call',
'keyup .keyup': '_call',
'change .change': '_call',
},
/**
* Call method for specific events
*
* @param {Event} event
* @return {*} the result returned by the handler function
*/
_call( event ) {
const _method = event.currentTarget.dataset.fn;
if ( typeof this[ _method ] === 'function' ) {
return this[ _method ].call( this, event, event.currentTarget );
}
},
/**
* Called after the view has been render
*
* @return {this} fluent interface
*/
afterRender() {
return this;
},
} );
} )( jQuery );

View File

@@ -0,0 +1,14 @@
module.exports = require( './base' ).extend( {
template: 'modals/edit',
afterInitialize() {
this.listenTo( this.collection, 'reset', _.bind( this.close, this ) );
},
afterRender() {
this.$_content = this.$el;
this.$( '.tvd-content-set-form' ).html( ( new TD_SETS.Views.Form( {
model: this.model,
collection: this.collection
} ) ).render().$el );
},
} );

View File

@@ -0,0 +1,32 @@
module.exports = require( './base' ).extend( {
template: 'tva-c-s-pagination',
afterInitialize() {
this.listenTo( this.collection, 'reset', this.render );
this.listenTo( this.collection, 'page-changed', this.render );
},
/**
* Toggle Pagination
* @param {boolean} status
*/
toggle( status ) {
this.$el.toggle( !! status );
return this;
},
render() {
const pageInfo = this.collection.pageInfo();
this.$el.html( pageInfo.totalPages > 1 ? TVE_Dash.tpl( this.template )( pageInfo ) : '' );
},
/**
* Callback in case prev button is clicked
*/
previousPage() {
this.collection.prev().trigger( 'page-changed' );
},
/**
* Callback in case next button is clicked
*/
nextPage() {
this.collection.next().trigger( 'page-changed' );
},
} );

View File

@@ -0,0 +1,255 @@
module.exports = require( './base' ).extend( {
template: 'rule',
className: 'tvd-content-set-rule',
hiddenPostTypes: [ 'tvd_blog_page', 'tvd_search_result_page' ],
afterInitialize() {
this.listenTo( this.model, 'control-changed', _.bind( this.stepChanged, this ) )
this.listenTo( this.collection, 'add', _.bind( this.allowDelete, this ) );
this.listenTo( this.collection, 'remove', _.bind( this.allowDelete, this ) );
},
clearSteps() {
if ( this.stepInstances ) {
this.stepInstances.forEach( inst => {
inst.off( 'all' );
inst.destroy();
} );
}
this.stepInstances = [];
},
afterRender() {
this.$ruleWrapper = this.$( '.tvd-rule-holder' ).empty();
this.$deleteRuleWrapper = this.$( '.tvd-content-set-delete-rule-wrapper' );
this.clearSteps();
if ( this.model.isEmpty() ) {
this.computeNextStep();
} else {
this.model.getSteps().forEach( step => {
if ( this.model.get( step ).length > 0 ) {
this.addStep( step );
}
} );
this.computeNextStep();
}
this.allowDelete();
},
stepChanged( stepChanged, stepChangedValue ) {
this.clearSteps();
this.$ruleWrapper.empty();
const removeStepFromIndex = this.getStepIndex( stepChanged ) + 1;
if ( this.getStepsLength() !== removeStepFromIndex ) {
for ( let i = removeStepFromIndex; i < this.getStepsLength(); i ++ ) {
const step = this.model.getSteps()[ i ];
if ( this.model.get( step ).toString().length ) {
const obj = {};
obj[ step ] = '';
this.model.set( obj, {silent: true} );
}
}
}
this.model.set( this.getRuleModelParams( stepChanged, stepChangedValue ) );
this.model.getSteps().forEach( step => {
if ( this.model.get( step ).length > 0 ) {
this.addStep( step );
}
} );
this.computeNextStep( 'field' === stepChanged && this.getNextStep().length );
},
/**
* Do some particular things here
* Based on the step, return the proper params
*
* @param {string} step
* @param {string} value
* @return {Object}
*/
getRuleModelParams( step, value ) {
const params = {};
params[ step ] = value;
if ( step === 'content' ) {
if ( [ 'tvd_search_result_page', 'tvd_blog_page' ].includes( params.content ) ) {
params.content_type = params.content;
params.field = - 1;
} else if ( params.content === 'archive' ) {
params.content_type = params.content;
} else {
params.content_type = TD_SETS.post_types.includes( value ) ? 'post' : 'term';
}
}
if ( step === 'field' ) {
if ( params.field === 'title' && this.model.get( 'content_type' ) === 'archive' ) {
params.content_type = 'term';
params.content = 'category';
}
if ( params.field === 'author' && [ 'archive', 'term' ].includes( this.model.get( 'content_type' ) ) ) {
params.content_type = 'archive';
params.content = 'archive';
}
}
return params
},
/**
* Computes the next step in building the rule
*
* @param {boolean} triggerChange
*/
computeNextStep( triggerChange = false ) {
const nextStep = this.getNextStep();
if ( nextStep.length ) {
this.addStep( this.getNextStep(), triggerChange );
}
},
getStepIndex( step ) {
return this.model.getSteps().indexOf( step );
},
getStepsLength() {
return this.model.getSteps().length;
},
/**
* Returns the next step in the rule
*
* @return {string}
*/
getNextStep() {
let nextStep = '';
if ( this.forceCompleteSteps() ) {
return nextStep;
}
this.model.getSteps().some( step => {
if ( this.model.get( step ).length === 0 ) {
nextStep = step;
return true;
}
} );
return nextStep;
},
/**
* Force complete steps
* Ex: when the field dropdown has "ALL" in it
*
* @return {boolean}
*/
forceCompleteSteps() {
return ( this.model.get( 'field' ) && parseInt( this.model.get( 'field' ) ) === - 1 ) || this.hiddenPostTypes.includes( this.model.get( 'content' ) );
},
/**
* Add a new step in the rule
*
* @param {string} step
* @param {boolean} triggerChange
*/
addStep( step, triggerChange = false ) {
const View = this.getStepView( step );
const instance = new View( {
ruleModel: this.model,
step,
model: new Backbone.Model( {
options: this.getStepOptions( step ),
trigger_change: triggerChange,
} )
} );
if ( step === 'content' && this.model.get( 'content' ) === 'category' ) {
instance.getStoredValue = function () {
if ( this.ruleModel.get( 'content_type' ) === 'term' && this.ruleModel.get( 'content' ) === 'category' ) {
return 'archive';
}
return this.ruleModel.get( this.step );
}
}
this.stepInstances.push( instance );
this.$ruleWrapper.append( instance.render().$el );
},
/**
* Do some particular things
*
* @param {string} step
*
* @return {*}
*/
getStepOptions( step ) {
let options = TD_SETS.options.general[ step ];
if ( step === 'field' && Array.isArray( TD_SETS.options.exceptions[ this.model.get( 'content' ) ] ) ) {
options = TD_SETS.options.exceptions[ this.model.get( 'content' ) ];
} else if ( step === 'operator' && Array.isArray( TD_SETS.options.exceptions[ this.model.get( 'field' ) ] ) ) {
options = TD_SETS.options.exceptions[ this.model.get( 'field' ) ];
}
return options;
},
/**
* Returns the Step View
*
* @param {string} step
*
* @return {Backbone.View}
*/
getStepView( step ) {
let View = require( './base' );
switch ( step ) {
case 'content':
case 'field':
case 'operator':
View = require( `./controls/select` );
break;
case 'value':
if ( this.model.get( 'field' ) === TD_SETS.fields.published_date ) {
if ( this.model.get( 'operator' ) === TD_SETS.operators.within_last ) {
View = require( `./controls/within-the-last` );
} else {
View = require( `./controls/date-picker` );
}
} else {
View = require( `./controls/select-multiple` );
}
default:
break;
}
return View;
},
allowDelete() {
this.$deleteRuleWrapper.toggle( this.collection.length > 1 );
},
deleteRule() {
const confirmView = new TD_SETS.Views.Confirm( {
template: TVE_Dash.tpl( 'rule-delete-confirmation' ),
className: 'tvd-content-set-delete-confirmation',
confirm: () => {
this.model.destroy();
this.destroy().remove();
},
cancel() {
this.remove();
}
} );
this.$el.append( confirmView.render().$el );
}
} );

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e){e(function(){TD.matches=new Backbone.Collection(TD.matches||[]),TD.sets=new Backbone.Collection(TD.sets||[]),new t({el:e("#tvd-contents-sets")})});const t=Backbone.View.extend({events:{"click .removeMatchedTag":e=>{const t=parseInt(e.currentTarget.parentNode.getAttribute("data-id")),n=TD.matches.findWhere({id:t});n&&TD.matches.remove(n)}},initialize:function(t){e.extend(this,!0,t),this.listenTo(TD.matches,"add remove",this.renderMatchedTags),this.$autocomplete=this.$("#tvd-content-sets-autocomplete"),this.$matchedWrapper=this.$("#tvd-matched-content-sets"),this.renderMatchedTags()},renderMatchedTags:function(){this.$matchedWrapper.empty();const e=[];TD.matches.each(t=>{this.$matchedWrapper.append(`<div data-id="${t.get("id")}"><span>${t.get("text")}</span><span class="removeMatchedTag"></span></div>`),e.push(t.get("id"))}),this.bindAutocomplete(),e.length&&this.$matchedWrapper.append(`<input type="hidden" name="tvd_matched_content_sets_id" value="${e.toString()}" />`)},_escape:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},bindAutocomplete:function(){this.$autocomplete.data("autocomplete")&&(this.$autocomplete.autocomplete("destroy"),this.$autocomplete.removeData("autocomplete")),this.$autocomplete.autocomplete({appendTo:this.$autocomplete.parent(),minLength:0,classes:{"ui-autocomplete":"tvd-content-sets-dropdown"},source:(t,n)=>{const a=new RegExp(this._escape(t.term),"i"),s=TD.sets.toJSON().filter(e=>void 0===TD.matches.findWhere({id:e.id})),o=0===t.term.trim().length?s:e.grep(s,function(e){return a.test(e.text)});n(e.map(o,function(e){return{label:e.text,key:e.id}}))},select:(e,t)=>{this.$autocomplete.val("");const n=TD.sets.findWhere({id:t.item.key});return n&&TD.matches.add(n.clone()),!1}})}})}(jQuery);

View File

@@ -0,0 +1,203 @@
var TVD_SS = TVD_SS || {};
// phpcs:disable
( function ( $ ) {
var userDataWithLink = [ 'username', 'first_name', 'last_name' ];
if ( typeof TVE !== 'undefined' ) {
TVE.add_filter( 'tcb.inline_shortcodes.insert', tvdShapeShortcode );
TVE.add_filter( 'tve.froala.shortcode.init', tvdBackwardsCompat );
TVE.add_filter( 'tve.shortcode.options.html', tvdShortcodeHtmlOptions );
TVE.add_action( 'tcb.froala.after_shortcode_select', tvdShortcodeSelect );
TVE.add_filter( 'tcb.inline_shortcodes.shortcode_value', tvdBeforeInsert );
}
/**
* Change the content on insert of inline-shortcode
* @param content - to be inserted
* @param shortcodeData
* @returns {string}
*/
function tvdBeforeInsert( content, shortcodeData ) {
/* wrap the content so it behaves like a link */
if ( shortcodeData.key === 'thrv_dynamic_data_user' && userDataWithLink.includes( shortcodeData.extra_key ) ) {
const link = shortcodeData.configOptions.find( opt => opt.key === 'link' );
if ( link && link.value === '1' ) {
content = '<a href="#">' + content + '</a>'
}
} else if ( shortcodeData.key === 'thrive_global_fields' ) {
if ( ! content.includes( 'thrive-shortcode-notice' ) ) {
const call = shortcodeData.configOptions.find( opt => opt.key === 'enable_call' );
const mail = shortcodeData.configOptions.find( opt => opt.key === 'enable_email' );
if ( call && call.value === '1' ) {
content = `<a href="tel:${content}">${content}</a>`
} else if ( mail && mail.value === '1' ) {
content = `<a href="mailto:${content}">${content}</a>`
}
}
}
return content;
}
/**
* Change the select template for Global fields
* @param html
* @param key
* @returns {*}
*/
function tvdShortcodeHtmlOptions( html, key ) {
if ( key === 'Global fields' ) {
html = TVE.tpl( 'inline/shortcodes/global-fields-options' )( {
shortcodes: TVE.CONST.inline_shortcodes[ key ]
} );
}
return html;
}
/**
* Custom handling for global fields inline shortcodes select
* @param $FE
* @param data
*/
function tvdShortcodeSelect( $FE, data ) {
if ( data && data.selectedData ) {
var selectedData = data.selectedData;
if ( selectedData.key === 'thrive_global_fields' ) {
var selectValue;
if ( ! selectedData.configOptions ) {
selectValue = $FE.find( '#fr-dropdown-shortcode-list' ).find( 'option:selected' ).attr( 'data-field-value' );
} else {
selectValue = selectedData.configOptions.find( function ( object ) {
return object.key === 'id';
} ).value;
/* Set the proper value for the first select on shortcode is already inserted */
$FE.find( `#fr-dropdown-shortcode-list option[data-field-value="${selectValue}"]` ).prop( 'selected', true );
}
/* hide the actual shortcode select but set the new value to make sure that shortcode is properly generated */
$FE.find( '#fr-dropdown-list-id' ).val( selectValue ).trigger( 'change' ).hide();
$FE.find( 'label[for="fr-dropdown-list-id"]' ).hide();
}
}
}
/**
* Replace old username, first_name, last_name shortcode with the new ones and make sure the options set are kept
* @param {HTMLElement} shortcode
* @returns {HTMLElement}
*/
function tvdBackwardsCompat( shortcode ) {
var shortcodeName = shortcode.getAttribute( 'data-shortcode' ),
compatShortcode,
cfg = {
key: 'thrv_dynamic_data_user'
};
if ( [ 'tcb_username_field', 'tcb_first_name_field', 'tcb_last_name_field' ].includes( shortcodeName ) ) {
shortcode.setAttribute( 'data-attr-link', shortcode.getAttribute( 'data-attr-link_to_profile' ) );
shortcode.setAttribute( 'data-attr-default', shortcode.getAttribute( 'data-attr-text_not_logged' ) );
shortcode.removeAttribute( 'data-attr-link_to_profile' );
shortcode.removeAttribute( 'data-attr-text_not_logged' );
switch ( shortcodeName ) {
case 'tcb_username_field':
cfg.extra_key = 'username';
break;
case 'tcb_first_name_field':
cfg.extra_key = 'first_name';
break;
case 'tcb_last_name_field':
cfg.extra_key = 'last_name';
break;
default:
break;
}
shortcode.setAttribute( 'data-attr-id', cfg.extra_key );
shortcode.setAttribute( 'data-extra_key', cfg.extra_key );
compatShortcode = TVE.inlineShortcodeFn.getShortcodeByValue( cfg );
if ( compatShortcode ) {
shortcode.setAttribute( 'data-shortcode', compatShortcode.config.value );
shortcode.setAttribute( 'data-shortcode-name', compatShortcode.config.option );
shortcode.innerText = compatShortcode.config.input.id.real_data[ cfg.extra_key ];
}
}
return shortcode;
}
/**
* Before shortcode insertion we do some processing of the data which will later shape the shortcode element ( shortcodeData structure can be seen bellow )
*
* @param shortcodeData
* @returns {*}
*/
// shortcodeData = {
// key: shortcode_key,
// extra_key: shortcode_extra_key,
// name: name,
// shortcodeName: name,
// class: SHORTCODE_CLASS,
// content_class: SHORTCODE_CONTENT_CLASS,
// configOptions: [ )
// { )
// key: '', ) used for inputs that require further configuration
// value: '', ) these will generate inputs inside the froala shortcode dropdown
// } )
// ] )
// options: [ ]
// { ]
// key: '', ] used for additional information passed through the shortcode itself
// value: '', ] these don't do much but will b part of the final shortcode structure
// } ]
// ] ]
// };
function tvdShapeShortcode( shortcodeData ) {
var shortcode, name, shortcodeName;
_.each( TVE.CONST.inline_shortcodes, function ( group, group_name ) {
if ( ! shortcode ) {
shortcode = group.find( function ( item ) {
return shortcodeData.extra_key && item.extra_param === shortcodeData.extra_key;
} );
}
} );
if ( shortcode ) {
shortcodeName = shortcode.input.id.value[ shortcodeData.configOptions.find( function ( item ) {
return item.key === 'id';
} ).value ];
if ( shortcodeName ) {
shortcodeData.shortcodeName = '[' + shortcodeData.shortcodeName + '] ' + shortcodeName;
shortcodeData.name = shortcodeData.shortcodeName;
}
name = shortcode.input.id.real_data[ shortcodeData.configOptions.find( function ( item ) {
return item.key === 'id';
} ).value ];
if ( name ) {
shortcodeData.name = name;
}
var multiline = shortcodeData.configOptions.find( function ( item ) {
return item.key === 'multiline';
} );
if ( multiline && multiline.value ) {
shortcodeData.name = shortcodeData.name.split( ',' ).join( '<br/>' );
}
var defaultData = shortcodeData.configOptions.filter( opt => opt.key === 'default' )[ 0 ];
if ( defaultData && defaultData.value &&
( shortcodeData.key === 'thrv_dynamic_data_request' /* url querystrings cookie ...*/
|| ( shortcodeData.key === 'thrv_dynamic_data_user' && shortcodeData.name.toLowerCase().includes( 'wordpress' ) ) ) /* user data not defined*/
) {
shortcodeData.name = defaultData.value;
}
}
return shortcodeData;
}
} )( jQuery );