mirror of
https://ghproxy.net/https://github.com/AlxMedia/curver.git
synced 2025-08-28 09:25:45 +08:00
Update to Kirki 4.2.0
This commit is contained in:
parent
cbfd4f27e4
commit
77ecd4ca69
440 changed files with 6230 additions and 5211 deletions
21
functions/kirki/kirki-packages/control-repeater/LICENSE
Normal file
21
functions/kirki/kirki-packages/control-repeater/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 kirki-framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
2
functions/kirki/kirki-packages/control-repeater/dist/control.css
vendored
Normal file
2
functions/kirki/kirki-packages/control-repeater/dist/control.css
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.customize-control-repeater{position:relative}.customize-control-repeater .repeater-fields .repeater-row{background:#eee;border:1px solid #e5e5e5;margin-top:.5rem;position:relative}.customize-control-repeater .repeater-fields .repeater-row.minimized{border:1px solid #dfdfdf;padding:0}.customize-control-repeater .repeater-fields .repeater-row.minimized:hover{border:1px solid #e5e5e5}.customize-control-repeater .repeater-fields .repeater-row.minimized .repeater-row-content{display:none}.customize-control-repeater .repeater-fields .repeater-row label{clear:both;margin-bottom:12px}.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-,.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-radio-image input{display:none}.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-radio-image input img{border:1px solid transparent}.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-radio-image input:checked+label img{border:1px solid #3498db;-webkit-box-shadow:0 0 5px 2px rgba(0,0,0,.25);box-shadow:0 0 5px 2px rgba(0,0,0,.25)}.customize-control-repeater .repeater-fields .repeater-row .repeater-field:last-child{border-bottom:none;padding-bottom:0}.customize-control-repeater button.repeater-add{margin-top:1rem}.customize-control-repeater .repeater-row-content{background:#fff;padding:10px 15px}.customize-control-repeater .repeater-field{border-bottom:1px dotted #ccc;clear:both;margin-bottom:12px;padding-bottom:12px;width:100%}.customize-control-repeater .repeater-field .customize-control-description,.customize-control-repeater .repeater-field .customize-control-title{font-size:13px;line-height:normal}.customize-control-repeater .repeater-field.repeater-field-hidden{border:0;margin:0;padding:0}.customize-control-repeater .repeater-field-select select{margin-left:0}.customize-control-repeater .repeater-field-checkbox label{line-height:28px}.customize-control-repeater .repeater-field-checkbox input{line-height:28px;margin-right:5px}.customize-control-repeater .repeater-field-textarea textarea{resize:vertical;width:100%}.customize-control-repeater .repeater-row-header{word-wrap:break-word;background:#fff;border-bottom:1px solid #dfdfdf;height:auto;line-height:30px;min-height:20px;overflow:hidden;padding:10px 15px;position:relative}.customize-control-repeater .repeater-row-header:hover{cursor:move}.customize-control-repeater .repeater-row-header .dashicons{color:#a0a5aa;font-size:18px;position:absolute;right:12px;top:2px}.customize-control-repeater .repeater-row-label{display:block;font-size:13px;font-weight:600;height:18px;line-height:20px;overflow:hidden;width:90%}.customize-control-repeater .repeater-row-remove{color:#a00}.customize-control-repeater .repeater-row-remove:hover{color:red}.customize-control-repeater .repeater-minimize{line-height:36px}.customize-control-repeater .remove-button,.customize-control-repeater .upload-button{width:48%}.kirki-image-attachment{margin:0 0 10px;text-align:center}.kirki-image-attachment img{display:inline-block}.kirki-file-attachment{margin:0 0 10px;text-align:center}.kirki-file-attachment .file{background:#f9f9f9;border:1px dotted #c3c3c3;display:block;padding:10px 5px}.limit{border-radius:3px;padding:3px}.limit.highlight{background:#d32f2f;color:#fff}
|
||||
/*# sourceMappingURL=control.css.map */
|
1
functions/kirki/kirki-packages/control-repeater/dist/control.css.map
vendored
Normal file
1
functions/kirki/kirki-packages/control-repeater/dist/control.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
functions/kirki/kirki-packages/control-repeater/dist/control.js
vendored
Normal file
2
functions/kirki/kirki-packages/control-repeater/dist/control.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
functions/kirki/kirki-packages/control-repeater/dist/control.js.map
vendored
Normal file
1
functions/kirki/kirki-packages/control-repeater/dist/control.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
11
functions/kirki/kirki-packages/control-repeater/dist/wp-color-picker-alpha.min.js
vendored
Normal file
11
functions/kirki/kirki-packages/control-repeater/dist/wp-color-picker-alpha.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,552 @@
|
|||
<?php
|
||||
/**
|
||||
* Customizer Control: repeater.
|
||||
*
|
||||
* @package kirki-framework/control-repeater
|
||||
* @copyright Copyright (c) 2023, Themeum
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
namespace Kirki\Control;
|
||||
|
||||
use Kirki\Control\Base;
|
||||
use Kirki\URL;
|
||||
/**
|
||||
* Repeater control
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
class Repeater extends Base {
|
||||
|
||||
/**
|
||||
* The control type.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'repeater';
|
||||
|
||||
/**
|
||||
* The fields that each container row will contain.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
public $fields = [];
|
||||
|
||||
/**
|
||||
* Will store a filtered version of value for advenced fields (like images).
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
protected $filtered_value = [];
|
||||
|
||||
/**
|
||||
* The row label
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
public $row_label = [];
|
||||
|
||||
/**
|
||||
* The button label
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public $button_label = '';
|
||||
|
||||
/**
|
||||
* The version. Used in scripts & styles for cache-busting.
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public static $control_ver = '1.0.5';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Supplied `$args` override class property defaults.
|
||||
* If `$args['settings']` is not defined, use the $id as the setting ID.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param WP_Customize_Manager $manager Customizer bootstrap instance.
|
||||
* @param string $id Control ID.
|
||||
* @param array $args {@see WP_Customize_Control::__construct}.
|
||||
*/
|
||||
public function __construct( $manager, $id, $args = [] ) {
|
||||
|
||||
parent::__construct( $manager, $id, $args );
|
||||
|
||||
// Set up defaults for row labels.
|
||||
$this->row_label = [
|
||||
'type' => 'text',
|
||||
'value' => esc_attr__( 'row', 'kirki' ),
|
||||
'field' => false,
|
||||
];
|
||||
|
||||
// Validate row-labels.
|
||||
$this->row_label( $args );
|
||||
|
||||
if ( empty( $this->button_label ) ) {
|
||||
/* translators: %s represents the label of the row. */
|
||||
$this->button_label = sprintf( esc_html__( 'Add new %s', 'kirki' ), $this->row_label['value'] );
|
||||
}
|
||||
|
||||
if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) {
|
||||
$args['fields'] = [];
|
||||
}
|
||||
|
||||
// An array to store keys of fields that need to be filtered.
|
||||
$media_fields_to_filter = [];
|
||||
|
||||
foreach ( $args['fields'] as $key => $value ) {
|
||||
if ( ! isset( $value['default'] ) ) {
|
||||
$args['fields'][ $key ]['default'] = '';
|
||||
}
|
||||
if ( ! isset( $value['label'] ) ) {
|
||||
$args['fields'][ $key ]['label'] = '';
|
||||
}
|
||||
$args['fields'][ $key ]['id'] = $key;
|
||||
|
||||
// We check if the filed is an uploaded media ( image , file, video, etc.. ).
|
||||
if ( isset( $value['type'] ) ) {
|
||||
switch ( $value['type'] ) {
|
||||
case 'image':
|
||||
case 'cropped_image':
|
||||
case 'upload':
|
||||
// We add it to the list of fields that need some extra filtering/processing.
|
||||
$media_fields_to_filter[ $key ] = true;
|
||||
break;
|
||||
|
||||
case 'dropdown-pages':
|
||||
// If the field is a dropdown-pages field then add it to args.
|
||||
$dropdown = wp_dropdown_pages(
|
||||
[
|
||||
'name' => '',
|
||||
'echo' => 0,
|
||||
'show_option_none' => esc_html__( 'Select a Page', 'kirki' ),
|
||||
'option_none_value' => '0',
|
||||
'selected' => '',
|
||||
]
|
||||
);
|
||||
|
||||
// Hackily add in the data link parameter.
|
||||
$dropdown = str_replace( '<select', '<select data-field="' . esc_attr( $args['fields'][ $key ]['id'] ) . '"' . $this->get_link(), $dropdown ); // phpcs:ignore Generic.Formatting.MultipleStatementAlignment
|
||||
$args['fields'][ $key ]['dropdown'] = $dropdown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->fields = $args['fields'];
|
||||
|
||||
// Now we are going to filter the fields.
|
||||
// First we create a copy of the value that would be used otherwise.
|
||||
$this->filtered_value = $this->value();
|
||||
|
||||
if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) {
|
||||
|
||||
// We iterate over the list of fields.
|
||||
foreach ( $this->filtered_value as &$filtered_value_field ) {
|
||||
|
||||
if ( is_array( $filtered_value_field ) && ! empty( $filtered_value_field ) ) {
|
||||
|
||||
// We iterate over the list of properties for this field.
|
||||
foreach ( $filtered_value_field as $key => &$value ) {
|
||||
|
||||
// We check if this field was marked as requiring extra filtering (in this case image, cropped_images, upload).
|
||||
if ( array_key_exists( $key, $media_fields_to_filter ) ) {
|
||||
|
||||
// What follows was made this way to preserve backward compatibility.
|
||||
// The repeater control use to store the URL for images instead of the attachment ID.
|
||||
// We check if the value look like an ID (otherwise it's probably a URL so don't filter it).
|
||||
if ( is_numeric( $value ) ) {
|
||||
|
||||
// "sanitize" the value.
|
||||
$attachment_id = (int) $value;
|
||||
|
||||
// Try to get the attachment_url.
|
||||
$url = wp_get_attachment_url( $attachment_id );
|
||||
|
||||
$filename = basename( get_attached_file( $attachment_id ) );
|
||||
|
||||
// If we got a URL.
|
||||
if ( $url ) {
|
||||
|
||||
// 'id' is needed for form hidden value, URL is needed to display the image.
|
||||
$value = [
|
||||
'id' => $attachment_id,
|
||||
'url' => $url,
|
||||
'filename' => $filename,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue control related scripts/styles.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
|
||||
parent::enqueue();
|
||||
|
||||
// Enqueue the style.
|
||||
wp_enqueue_style( 'wp-color-picker' );
|
||||
wp_enqueue_style( 'kirki-control-repeater-style', URL::get_from_path( dirname( dirname( __DIR__ ) ) . '/dist/control.css' ), [], self::$control_ver );
|
||||
|
||||
// Enqueue the script.
|
||||
wp_enqueue_script( 'wp-color-picker-alpha', URL::get_from_path( dirname( dirname( __DIR__ ) ) . '/dist/wp-color-picker-alpha.min.js' ), array( 'jquery', 'customize-base', 'wp-color-picker' ), self::$control_ver, false );
|
||||
wp_enqueue_script( 'kirki-control-repeater', URL::get_from_path( dirname( dirname( __DIR__ ) ) . '/dist/control.js' ), [ 'wp-color-picker-alpha' ], self::$control_ver, false );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the parameters passed to the JavaScript via JSON.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function to_json() {
|
||||
|
||||
parent::to_json();
|
||||
|
||||
$fields = $this->fields;
|
||||
|
||||
$this->json['fields'] = $fields;
|
||||
$this->json['row_label'] = $this->row_label;
|
||||
|
||||
// If filtered_value has been set and is not empty we use it instead of the actual value.
|
||||
if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) {
|
||||
$this->json['value'] = $this->filtered_value;
|
||||
}
|
||||
|
||||
$this->json['value'] = apply_filters( "kirki_controls_repeater_value_{$this->id}", $this->json['value'] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the control's content.
|
||||
* Allows the content to be overriden without having to rewrite the wrapper in $this->render().
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function render_content() {
|
||||
|
||||
?>
|
||||
<label>
|
||||
<?php if ( ! empty( $this->label ) ) : ?>
|
||||
<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $this->description ) ) : ?>
|
||||
<span class="description customize-control-description"><?php echo wp_kses_post( $this->description ); ?></span>
|
||||
<?php endif; ?>
|
||||
<input type="hidden" {{{ data.inputAttrs }}} value="" <?php echo wp_kses_post( $this->get_link() ); ?> />
|
||||
</label>
|
||||
|
||||
<ul class="repeater-fields"></ul>
|
||||
|
||||
<?php if ( isset( $this->choices['limit'] ) ) : ?>
|
||||
<?php /* translators: %s represents the number of rows we're limiting the repeater to allow. */ ?>
|
||||
<p class="limit"><?php printf( esc_html__( 'Limit: %s rows', 'kirki' ), esc_html( $this->choices['limit'] ) ); ?></p>
|
||||
<?php endif; ?>
|
||||
<button class="button-secondary repeater-add"><?php echo esc_html( $this->button_label ); ?></button>
|
||||
|
||||
<?php
|
||||
$this->repeater_js_template();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An Underscore (JS) template for this control's content (but not its container).
|
||||
* Class variables for this control class are available in the `data` JS object.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function repeater_js_template() {
|
||||
?>
|
||||
|
||||
<script type="text/html" class="customize-control-repeater-content">
|
||||
<# var field; var index = data.index; #>
|
||||
|
||||
<li class="repeater-row minimized" data-row="{{{ index }}}">
|
||||
|
||||
<div class="repeater-row-header">
|
||||
<span class="repeater-row-label"></span>
|
||||
<i class="dashicons dashicons-arrow-down repeater-minimize"></i>
|
||||
</div>
|
||||
<div class="repeater-row-content">
|
||||
<# _.each( data, function( field, i ) { #>
|
||||
|
||||
<div class="repeater-field repeater-field-{{{ field.type }}} repeater-field-{{ field.id }}">
|
||||
|
||||
<# if ( 'text' === field.type || 'url' === field.type || 'link' === field.type || 'email' === field.type || 'tel' === field.type || 'date' === field.type || 'number' === field.type ) { #>
|
||||
<# var fieldExtras = ''; #>
|
||||
<# if ( 'link' === field.type ) { #>
|
||||
<# field.type = 'url' #>
|
||||
<# } #>
|
||||
|
||||
<# if ( 'number' === field.type ) { #>
|
||||
<# if ( ! _.isUndefined( field.choices ) && ! _.isUndefined( field.choices.min ) ) { #>
|
||||
<# fieldExtras += ' min="' + field.choices.min + '"'; #>
|
||||
<# } #>
|
||||
<# if ( ! _.isUndefined( field.choices ) && ! _.isUndefined( field.choices.max ) ) { #>
|
||||
<# fieldExtras += ' max="' + field.choices.max + '"'; #>
|
||||
<# } #>
|
||||
<# if ( ! _.isUndefined( field.choices ) && ! _.isUndefined( field.choices.step ) ) { #>
|
||||
<# fieldExtras += ' step="' + field.choices.step + '"'; #>
|
||||
<# } #>
|
||||
<# } #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<input type="{{field.type}}" name="" value="{{{ field.default }}}" data-field="{{{ field.id }}}"{{ fieldExtras }}>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'number' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<input type="{{ field.type }}" name="" value="{{{ field.default }}}" data-field="{{{ field.id }}}"{{ numberFieldExtras }}>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'hidden' === field.type ) { #>
|
||||
|
||||
<input type="hidden" data-field="{{{ field.id }}}" <# if ( field.default ) { #> value="{{{ field.default }}}" <# } #> />
|
||||
|
||||
<# } else if ( 'checkbox' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" value="{{{ field.default }}}" data-field="{{{ field.id }}}" <# if ( field.default ) { #> checked="checked" <# } #> /> {{{ field.label }}}
|
||||
<# if ( field.description ) { #>{{{ field.description }}}<# } #>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'select' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<select data-field="{{{ field.id }}}"<# if ( ! _.isUndefined( field.multiple ) && false !== field.multiple ) { #> multiple="multiple" data-multiple="{{ field.multiple }}"<# } #>>
|
||||
<# _.each( field.choices, function( choice, i ) { #>
|
||||
<option value="{{{ i }}}" <# if ( -1 !== jQuery.inArray( i, field.default ) || field.default == i ) { #> selected="selected" <# } #>>{{ choice }}</option>
|
||||
<# }); #>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'dropdown-pages' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ data.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<div class="customize-control-content repeater-dropdown-pages">{{{ field.dropdown }}}</div>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'radio' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
|
||||
<# _.each( field.choices, function( choice, i ) { #>
|
||||
<label><input type="radio" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>> {{ choice }} <br/></label>
|
||||
<# }); #>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'radio-image' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
|
||||
<# _.each( field.choices, function( choice, i ) { #>
|
||||
<input type="radio" id="{{{ field.id }}}_{{ index }}_{{{ i }}}" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>>
|
||||
<label for="{{{ field.id }}}_{{ index }}_{{{ i }}}"><img src="{{ choice }}"></label>
|
||||
</input>
|
||||
<# }); #>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'color' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
</label>
|
||||
|
||||
<#
|
||||
var defaultValue = '';
|
||||
if ( field.default ) {
|
||||
if ( -1 !== field.default.indexOf( 'rgb' ) || -1 !== field.default.indexOf( '#' ) ) {
|
||||
defaultValue = field.default;
|
||||
|
||||
if (-1 !== field.default.indexOf('rgba')) {
|
||||
if (!field.choices) field.choices = {};
|
||||
field.choices.alpha = true;
|
||||
}
|
||||
} else {
|
||||
if (field.default.length >= 3) {
|
||||
defaultValue = '#' + field.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
#>
|
||||
|
||||
<#
|
||||
var alphaEnabledAttr = '';
|
||||
if ( field.choices && field.choices.alpha ) {
|
||||
alphaEnabledAttr = ' data-alpha-enabled=true';
|
||||
}
|
||||
#>
|
||||
|
||||
<input class="kirki-classic-color-picker" type="text" maxlength="7" value="{{{ field.default }}}" data-field="{{{ field.id }}}" data-default-color="{{{ defaultValue }}}" {{alphaEnabledAttr}} />
|
||||
|
||||
<# } else if ( 'textarea' === field.type ) { #>
|
||||
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<textarea rows="5" data-field="{{{ field.id }}}">{{ field.default }}</textarea>
|
||||
|
||||
<# } else if ( field.type === 'image' || field.type === 'cropped_image' ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
</label>
|
||||
|
||||
<figure class="kirki-image-attachment" data-placeholder="<?php esc_attr_e( 'No Image Selected', 'kirki' ); ?>" >
|
||||
<# if ( field.default ) { #>
|
||||
<# var defaultImageURL = ( field.default.url ) ? field.default.url : field.default; #>
|
||||
<img src="{{{ defaultImageURL }}}">
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'No Image Selected', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</figure>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"><?php esc_html_e( 'Remove', 'kirki' ); ?></button>
|
||||
<button type="button" class="button upload-button" data-label=" <?php esc_attr_e( 'Add Image', 'kirki' ); ?>" data-alt-label="<?php echo esc_attr_e( 'Change Image', 'kirki' ); ?>" >
|
||||
<# if ( field.default ) { #>
|
||||
<?php esc_html_e( 'Change Image', 'kirki' ); ?>
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'Add Image', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</button>
|
||||
<# if ( field.default.id ) { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" >
|
||||
<# } else { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" >
|
||||
<# } #>
|
||||
</div>
|
||||
|
||||
<# } else if ( field.type === 'upload' ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
</label>
|
||||
|
||||
<figure class="kirki-file-attachment" data-placeholder="<?php esc_attr_e( 'No File Selected', 'kirki' ); ?>" >
|
||||
<# if ( field.default ) { #>
|
||||
<# var defaultFilename = ( field.default.filename ) ? field.default.filename : field.default; #>
|
||||
<span class="file"><span class="dashicons dashicons-media-default"></span> {{ defaultFilename }}</span>
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'No File Selected', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</figure>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"><?php esc_html_e( 'Remove', 'kirki' ); ?></button>
|
||||
<button type="button" class="button upload-button" data-label="<?php esc_attr_e( 'Add File', 'kirki' ); ?>" data-alt-label="<?php esc_attr_e( 'Change File', 'kirki' ); ?>">
|
||||
<# if ( field.default ) { #>
|
||||
<?php esc_html_e( 'Change File', 'kirki' ); ?>
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'Add File', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</button>
|
||||
<# if ( field.default.id ) { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" >
|
||||
<# } else { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" >
|
||||
<# } #>
|
||||
</div>
|
||||
|
||||
<# } else if ( 'custom' === field.type ) { #>
|
||||
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<div data-field="{{{ field.id }}}">{{{ field.default }}}</div>
|
||||
|
||||
<# } #>
|
||||
|
||||
</div>
|
||||
<# }); #>
|
||||
<button type="button" class="button-link repeater-row-remove"><?php esc_html_e( 'Remove', 'kirki' ); ?></button>
|
||||
</div>
|
||||
</li>
|
||||
</script>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate row-labels.
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @param array $args {@see WP_Customize_Control::__construct}.
|
||||
* @return void
|
||||
*/
|
||||
protected function row_label( $args ) {
|
||||
|
||||
// Validating args for row labels.
|
||||
if ( isset( $args['row_label'] ) && is_array( $args['row_label'] ) && ! empty( $args['row_label'] ) ) {
|
||||
|
||||
// Validating row label type.
|
||||
if ( isset( $args['row_label']['type'] ) && ( 'text' === $args['row_label']['type'] || 'field' === $args['row_label']['type'] ) ) {
|
||||
$this->row_label['type'] = $args['row_label']['type'];
|
||||
}
|
||||
|
||||
// Validating row label type.
|
||||
if ( isset( $args['row_label']['value'] ) && ! empty( $args['row_label']['value'] ) ) {
|
||||
$this->row_label['value'] = esc_html( $args['row_label']['value'] );
|
||||
}
|
||||
|
||||
// Validating row label field.
|
||||
if ( isset( $args['row_label']['field'] ) && ! empty( $args['row_label']['field'] ) && isset( $args['fields'][ sanitize_key( $args['row_label']['field'] ) ] ) ) {
|
||||
$this->row_label['field'] = esc_html( $args['row_label']['field'] );
|
||||
} else {
|
||||
// If from field is not set correctly, making sure standard is set as the type.
|
||||
$this->row_label['type'] = 'text';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
/**
|
||||
* Override field methods
|
||||
*
|
||||
* @package kirki-framework/control-repeater
|
||||
* @copyright Copyright (c) 2023, Themeum
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
namespace Kirki\Field;
|
||||
|
||||
use Kirki\Compatibility\Field;
|
||||
use Kirki\Field\Upload;
|
||||
|
||||
/**
|
||||
* Field overrides.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
class Repeater extends Field {
|
||||
|
||||
/**
|
||||
* The field type.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'kirki-repeater';
|
||||
|
||||
/**
|
||||
* Used only on repeaters.
|
||||
* Contains an array of the fields.
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* Sets the control type.
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function set_type() {
|
||||
|
||||
$this->type = 'repeater';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the $transport
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function set_transport() {
|
||||
|
||||
// Force using refresh mode.
|
||||
// Currently the repeater control does not support postMessage.
|
||||
$this->transport = 'refresh';
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the $sanitize_callback
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function set_sanitize_callback() {
|
||||
|
||||
if ( empty( $this->sanitize_callback ) ) {
|
||||
$this->sanitize_callback = [ $this, 'sanitize' ];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The sanitize method that will be used as a falback
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param string|array $value The control's value.
|
||||
*/
|
||||
public function sanitize( $value ) {
|
||||
|
||||
// is the value formatted as a string?
|
||||
if ( is_string( $value ) ) {
|
||||
$value = rawurldecode( $value );
|
||||
$value = json_decode( $value, true );
|
||||
}
|
||||
|
||||
// Nothing to sanitize if we don't have fields.
|
||||
if ( empty( $this->fields ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
foreach ( $value as $row_id => $row_value ) {
|
||||
|
||||
// Make sure the row is formatted as an array.
|
||||
if ( ! is_array( $row_value ) ) {
|
||||
$value[ $row_id ] = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start parsing sub-fields in rows.
|
||||
foreach ( $row_value as $subfield_id => $subfield_value ) {
|
||||
|
||||
// Make sure this is a valid subfield.
|
||||
// If it's not, then unset it.
|
||||
if ( ! isset( $this->fields[ $subfield_id ] ) ) {
|
||||
unset( $value[ $row_id ][ $subfield_id ] );
|
||||
}
|
||||
|
||||
// Get the subfield-type.
|
||||
if ( ! isset( $this->fields[ $subfield_id ]['type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subfield = $this->fields[ $subfield_id ];
|
||||
$subfield_type = $subfield['type'];
|
||||
|
||||
// Allow using a sanitize-callback on a per-field basis.
|
||||
if ( isset( $this->fields[ $subfield_id ]['sanitize_callback'] ) ) {
|
||||
$subfield_value = call_user_func( $this->fields[ $subfield_id ]['sanitize_callback'], $subfield_value );
|
||||
} else {
|
||||
|
||||
switch ( $subfield_type ) {
|
||||
case 'image':
|
||||
case 'cropped_image':
|
||||
case 'upload':
|
||||
$save_as = isset( $subfield['choices'] ) && isset( $subfield['choices']['save_as'] ) ? $subfield['choices']['save_as'] : 'url';
|
||||
$subfield_value = Upload::sanitize( $subfield_value, $save_as );
|
||||
|
||||
break;
|
||||
case 'dropdown-pages':
|
||||
$subfield_value = (int) $subfield_value;
|
||||
break;
|
||||
case 'color':
|
||||
if ( $subfield_value ) {
|
||||
$subfield_value = \Kirki\Field\ReactColorful::sanitize( $subfield_value );
|
||||
}
|
||||
break;
|
||||
case 'text':
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
break;
|
||||
case 'url':
|
||||
case 'link':
|
||||
$subfield_value = esc_url_raw( $subfield_value );
|
||||
break;
|
||||
case 'email':
|
||||
$subfield_value = filter_var( $subfield_value, FILTER_SANITIZE_EMAIL );
|
||||
break;
|
||||
case 'tel':
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
break;
|
||||
case 'checkbox':
|
||||
$subfield_value = (bool) $subfield_value;
|
||||
break;
|
||||
case 'select':
|
||||
if ( isset( $this->fields[ $subfield_id ]['multiple'] ) ) {
|
||||
if ( true === $this->fields[ $subfield_id ]['multiple'] ) {
|
||||
$multiple = 2;
|
||||
}
|
||||
$multiple = (int) $this->fields[ $subfield_id ]['multiple'];
|
||||
if ( 1 < $multiple ) {
|
||||
$subfield_value = (array) $subfield_value;
|
||||
foreach ( $subfield_value as $sub_subfield_key => $sub_subfield_value ) {
|
||||
$subfield_value[ $sub_subfield_key ] = sanitize_text_field( $sub_subfield_value );
|
||||
}
|
||||
} else {
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'radio':
|
||||
case 'radio-image':
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
break;
|
||||
case 'textarea':
|
||||
$subfield_value = html_entity_decode( wp_kses_post( $subfield_value ) );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$value[ $row_id ][ $subfield_id ] = $subfield_value;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
/**
|
||||
* Repeater Customizer Setting.
|
||||
*
|
||||
* @package kirki-framework/control-repeater
|
||||
* @copyright Copyright (c) 2023, Themeum
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
namespace Kirki\Settings;
|
||||
|
||||
/**
|
||||
* Repeater Settings.
|
||||
*/
|
||||
class Repeater extends \WP_Customize_Setting {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Any supplied $args override class property defaults.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param WP_Customize_Manager $manager The WordPress WP_Customize_Manager object.
|
||||
* @param string $id A specific ID of the setting. Can be a theme mod or option name.
|
||||
* @param array $args Setting arguments.
|
||||
*/
|
||||
public function __construct( $manager, $id, $args = [] ) {
|
||||
parent::__construct( $manager, $id, $args );
|
||||
|
||||
// Will convert the setting from JSON to array. Must be triggered very soon.
|
||||
add_filter( "customize_sanitize_{$this->id}", [ $this, 'sanitize_repeater_setting' ], 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the value of the setting.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return mixed The value.
|
||||
*/
|
||||
public function value() {
|
||||
return (array) parent::value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the JSON encoded setting coming from Customizer to an Array.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param string $value URL Encoded JSON Value.
|
||||
* @return array
|
||||
*/
|
||||
public function sanitize_repeater_setting( $value ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
$value = json_decode( urldecode( $value ) );
|
||||
}
|
||||
|
||||
if ( empty( $value ) || ! is_array( $value ) ) {
|
||||
$value = [];
|
||||
}
|
||||
|
||||
// Make sure that every row is an array, not an object.
|
||||
foreach ( $value as $key => $val ) {
|
||||
$value[ $key ] = (array) $val;
|
||||
if ( empty( $val ) ) {
|
||||
unset( $value[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Reindex array.
|
||||
if ( is_array( $value ) ) {
|
||||
$value = array_values( $value );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
931
functions/kirki/kirki-packages/control-repeater/src/control.js
Normal file
931
functions/kirki/kirki-packages/control-repeater/src/control.js
Normal file
|
@ -0,0 +1,931 @@
|
|||
import "./control.scss";
|
||||
|
||||
/* global kirkiControlLoader */
|
||||
/* eslint max-depth: 0 */
|
||||
/* eslint no-useless-escape: 0 */
|
||||
var RepeaterRow = function (rowIndex, container, label, control) {
|
||||
var self = this;
|
||||
this.rowIndex = rowIndex;
|
||||
this.container = container;
|
||||
this.label = label;
|
||||
this.header = this.container.find(".repeater-row-header");
|
||||
|
||||
this.header.on("click", function () {
|
||||
self.toggleMinimize();
|
||||
});
|
||||
|
||||
this.container.on("click", ".repeater-row-remove", function () {
|
||||
self.remove();
|
||||
});
|
||||
|
||||
this.header.on("mousedown", function () {
|
||||
self.container.trigger("row:start-dragging");
|
||||
});
|
||||
|
||||
this.container.on("keyup change", "input, select, textarea", function (e) {
|
||||
self.container.trigger("row:update", [
|
||||
self.rowIndex,
|
||||
jQuery(e.target).data("field"),
|
||||
e.target,
|
||||
]);
|
||||
});
|
||||
|
||||
this.setRowIndex = function (rowNum) {
|
||||
this.rowIndex = rowNum;
|
||||
this.container.attr("data-row", rowNum);
|
||||
this.container.data("row", rowNum);
|
||||
this.updateLabel();
|
||||
};
|
||||
|
||||
this.toggleMinimize = function () {
|
||||
// Store the previous state.
|
||||
this.container.toggleClass("minimized");
|
||||
this.header
|
||||
.find(".dashicons")
|
||||
.toggleClass("dashicons-arrow-up")
|
||||
.toggleClass("dashicons-arrow-down");
|
||||
};
|
||||
|
||||
this.remove = function () {
|
||||
this.container.slideUp(300, function () {
|
||||
jQuery(this).detach();
|
||||
});
|
||||
this.container.trigger("row:remove", [this.rowIndex]);
|
||||
};
|
||||
|
||||
this.updateLabel = function () {
|
||||
var rowLabelField, rowLabel, rowLabelSelector;
|
||||
|
||||
if ("field" === this.label.type) {
|
||||
rowLabelField = this.container.find(
|
||||
'.repeater-field [data-field="' + this.label.field + '"]'
|
||||
);
|
||||
if (_.isFunction(rowLabelField.val)) {
|
||||
rowLabel = rowLabelField.val();
|
||||
if ("" !== rowLabel) {
|
||||
if (!_.isUndefined(control.params.fields[this.label.field])) {
|
||||
if (!_.isUndefined(control.params.fields[this.label.field].type)) {
|
||||
if ("select" === control.params.fields[this.label.field].type) {
|
||||
if (
|
||||
!_.isUndefined(
|
||||
control.params.fields[this.label.field].choices
|
||||
) &&
|
||||
!_.isUndefined(
|
||||
control.params.fields[this.label.field].choices[
|
||||
rowLabelField.val()
|
||||
]
|
||||
)
|
||||
) {
|
||||
rowLabel =
|
||||
control.params.fields[this.label.field].choices[
|
||||
rowLabelField.val()
|
||||
];
|
||||
}
|
||||
} else if (
|
||||
"radio" === control.params.fields[this.label.field].type ||
|
||||
"radio-image" === control.params.fields[this.label.field].type
|
||||
) {
|
||||
rowLabelSelector =
|
||||
control.selector +
|
||||
' [data-row="' +
|
||||
this.rowIndex +
|
||||
'"] .repeater-field [data-field="' +
|
||||
this.label.field +
|
||||
'"]:checked';
|
||||
rowLabel = jQuery(rowLabelSelector).val();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.header.find(".repeater-row-label").text(rowLabel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.header
|
||||
.find(".repeater-row-label")
|
||||
.text(this.label.value + " " + (this.rowIndex + 1));
|
||||
};
|
||||
this.updateLabel();
|
||||
};
|
||||
|
||||
wp.customize.controlConstructor.repeater = wp.customize.Control.extend({
|
||||
// When we're finished loading continue processing
|
||||
ready: function () {
|
||||
var control = this;
|
||||
|
||||
// Init the control.
|
||||
if (
|
||||
!_.isUndefined(window.kirkiControlLoader) &&
|
||||
_.isFunction(kirkiControlLoader)
|
||||
) {
|
||||
kirkiControlLoader(control);
|
||||
} else {
|
||||
control.initKirkiControl();
|
||||
}
|
||||
},
|
||||
|
||||
initKirkiControl: function (control) {
|
||||
var limit, theNewRow, settingValue;
|
||||
control = control || this;
|
||||
|
||||
// The current value set in Control Class (set in Kirki_Customize_Repeater_Control::to_json() function)
|
||||
settingValue = control.params.value;
|
||||
|
||||
// The hidden field that keeps the data saved (though we never update it)
|
||||
control.settingField = control.container
|
||||
.find("[data-customize-setting-link]")
|
||||
.first();
|
||||
|
||||
// Set the field value for the first time, we'll fill it up later
|
||||
control.setValue([], false);
|
||||
|
||||
// The DIV that holds all the rows
|
||||
control.repeaterFieldsContainer = control.container
|
||||
.find(".repeater-fields")
|
||||
.first();
|
||||
|
||||
// Set number of rows to 0
|
||||
control.currentIndex = 0;
|
||||
|
||||
// Save the rows objects
|
||||
control.rows = [];
|
||||
|
||||
// Default limit choice
|
||||
limit = false;
|
||||
if (!_.isUndefined(control.params.choices.limit)) {
|
||||
limit =
|
||||
0 >= control.params.choices.limit
|
||||
? false
|
||||
: parseInt(control.params.choices.limit, 10);
|
||||
}
|
||||
|
||||
control.container.on("click", "button.repeater-add", function (e) {
|
||||
e.preventDefault();
|
||||
if (!limit || control.currentIndex < limit) {
|
||||
theNewRow = control.addRow();
|
||||
theNewRow.toggleMinimize();
|
||||
control.initColorPicker();
|
||||
control.initSelect(theNewRow);
|
||||
} else {
|
||||
jQuery(control.selector + " .limit").addClass("highlight");
|
||||
}
|
||||
});
|
||||
|
||||
control.container.on("click", ".repeater-row-remove", function () {
|
||||
control.currentIndex--;
|
||||
if (!limit || control.currentIndex < limit) {
|
||||
jQuery(control.selector + " .limit").removeClass("highlight");
|
||||
}
|
||||
});
|
||||
|
||||
control.container.on(
|
||||
"click keypress",
|
||||
".repeater-field-image .upload-button,.repeater-field-cropped_image .upload-button,.repeater-field-upload .upload-button",
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
control.$thisButton = jQuery(this);
|
||||
control.openFrame(e);
|
||||
}
|
||||
);
|
||||
|
||||
control.container.on(
|
||||
"click keypress",
|
||||
".repeater-field-image .remove-button,.repeater-field-cropped_image .remove-button",
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
control.$thisButton = jQuery(this);
|
||||
control.removeImage(e);
|
||||
}
|
||||
);
|
||||
|
||||
control.container.on(
|
||||
"click keypress",
|
||||
".repeater-field-upload .remove-button",
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
control.$thisButton = jQuery(this);
|
||||
control.removeFile(e);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Function that loads the Mustache template
|
||||
*/
|
||||
control.repeaterTemplate = _.memoize(function () {
|
||||
var compiled,
|
||||
/*
|
||||
* Underscore's default ERB-style templates are incompatible with PHP
|
||||
* when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax.
|
||||
*
|
||||
* @see trac ticket #22344.
|
||||
*/
|
||||
options = {
|
||||
evaluate: /<#([\s\S]+?)#>/g,
|
||||
interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
|
||||
escape: /\{\{([^\}]+?)\}\}(?!\})/g,
|
||||
variable: "data",
|
||||
};
|
||||
|
||||
return function (data) {
|
||||
compiled = _.template(
|
||||
control.container
|
||||
.find(".customize-control-repeater-content")
|
||||
.first()
|
||||
.html(),
|
||||
null,
|
||||
options
|
||||
);
|
||||
return compiled(data);
|
||||
};
|
||||
});
|
||||
|
||||
// When we load the control, the fields have not been filled up
|
||||
// This is the first time that we create all the rows
|
||||
if (settingValue.length) {
|
||||
_.each(settingValue, function (subValue) {
|
||||
theNewRow = control.addRow(subValue);
|
||||
control.initColorPicker();
|
||||
control.initSelect(theNewRow, subValue);
|
||||
});
|
||||
}
|
||||
|
||||
control.repeaterFieldsContainer.sortable({
|
||||
handle: ".repeater-row-header",
|
||||
update: function () {
|
||||
control.sort();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media modal.
|
||||
*
|
||||
* @param {Object} event - The JS event.
|
||||
* @returns {void}
|
||||
*/
|
||||
openFrame: function (event) {
|
||||
if (wp.customize.utils.isKeydownButNotEnterEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.$thisButton
|
||||
.closest(".repeater-field")
|
||||
.hasClass("repeater-field-cropped_image")
|
||||
) {
|
||||
this.initCropperFrame();
|
||||
} else {
|
||||
this.initFrame();
|
||||
}
|
||||
|
||||
this.frame.open();
|
||||
},
|
||||
|
||||
initFrame: function () {
|
||||
var libMediaType = this.getMimeType();
|
||||
|
||||
this.frame = wp.media({
|
||||
states: [
|
||||
new wp.media.controller.Library({
|
||||
library: wp.media.query({ type: libMediaType }),
|
||||
multiple: false,
|
||||
date: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// When a file is selected, run a callback.
|
||||
this.frame.on("select", this.onSelect, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a media modal select frame, and store it so the instance can be reused when needed.
|
||||
* This is mostly a copy/paste of Core api.CroppedImageControl in /wp-admin/js/customize-control.js
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initCropperFrame: function () {
|
||||
// We get the field id from which this was called
|
||||
var currentFieldId = this.$thisButton
|
||||
.siblings("input.hidden-field")
|
||||
.attr("data-field"),
|
||||
attrs = ["width", "height", "flex_width", "flex_height"], // A list of attributes to look for
|
||||
libMediaType = this.getMimeType();
|
||||
|
||||
// Make sure we got it
|
||||
if (_.isString(currentFieldId) && "" !== currentFieldId) {
|
||||
// Make fields is defined and only do the hack for cropped_image
|
||||
if (
|
||||
_.isObject(this.params.fields[currentFieldId]) &&
|
||||
"cropped_image" === this.params.fields[currentFieldId].type
|
||||
) {
|
||||
//Iterate over the list of attributes
|
||||
attrs.forEach(
|
||||
function (el) {
|
||||
// If the attribute exists in the field
|
||||
if (!_.isUndefined(this.params.fields[currentFieldId][el])) {
|
||||
// Set the attribute in the main object
|
||||
this.params[el] = this.params.fields[currentFieldId][el];
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.frame = wp.media({
|
||||
button: {
|
||||
text: "Select and Crop",
|
||||
close: false,
|
||||
},
|
||||
states: [
|
||||
new wp.media.controller.Library({
|
||||
library: wp.media.query({ type: libMediaType }),
|
||||
multiple: false,
|
||||
date: false,
|
||||
suggestedWidth: this.params.width,
|
||||
suggestedHeight: this.params.height,
|
||||
}),
|
||||
new wp.media.controller.CustomizeImageCropper({
|
||||
imgSelectOptions: this.calculateImageSelectOptions,
|
||||
control: this,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.frame.on("select", this.onSelectForCrop, this);
|
||||
this.frame.on("cropped", this.onCropped, this);
|
||||
this.frame.on("skippedcrop", this.onSkippedCrop, this);
|
||||
},
|
||||
|
||||
onSelect: function () {
|
||||
var attachment = this.frame.state().get("selection").first().toJSON();
|
||||
|
||||
if (
|
||||
this.$thisButton
|
||||
.closest(".repeater-field")
|
||||
.hasClass("repeater-field-upload")
|
||||
) {
|
||||
this.setFileInRepeaterField(attachment);
|
||||
} else {
|
||||
this.setImageInRepeaterField(attachment);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* After an image is selected in the media modal, switch to the cropper
|
||||
* state if the image isn't the right size.
|
||||
*/
|
||||
|
||||
onSelectForCrop: function () {
|
||||
var attachment = this.frame.state().get("selection").first().toJSON();
|
||||
|
||||
if (
|
||||
this.params.width === attachment.width &&
|
||||
this.params.height === attachment.height &&
|
||||
!this.params.flex_width &&
|
||||
!this.params.flex_height
|
||||
) {
|
||||
this.setImageInRepeaterField(attachment);
|
||||
} else {
|
||||
this.frame.setState("cropper");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* After the image has been cropped, apply the cropped image data to the setting.
|
||||
*
|
||||
* @param {object} croppedImage Cropped attachment data.
|
||||
* @returns {void}
|
||||
*/
|
||||
onCropped: function (croppedImage) {
|
||||
this.setImageInRepeaterField(croppedImage);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a set of options, computed from the attached image data and
|
||||
* control-specific data, to be fed to the imgAreaSelect plugin in
|
||||
* wp.media.view.Cropper.
|
||||
*
|
||||
* @param {wp.media.model.Attachment} attachment - The attachment from the WP API.
|
||||
* @param {wp.media.controller.Cropper} controller - Media controller.
|
||||
* @returns {Object} - Options.
|
||||
*/
|
||||
calculateImageSelectOptions: function (attachment, controller) {
|
||||
var control = controller.get("control"),
|
||||
flexWidth = !!parseInt(control.params.flex_width, 10),
|
||||
flexHeight = !!parseInt(control.params.flex_height, 10),
|
||||
realWidth = attachment.get("width"),
|
||||
realHeight = attachment.get("height"),
|
||||
xInit = parseInt(control.params.width, 10),
|
||||
yInit = parseInt(control.params.height, 10),
|
||||
ratio = xInit / yInit,
|
||||
xImg = realWidth,
|
||||
yImg = realHeight,
|
||||
x1,
|
||||
y1,
|
||||
imgSelectOptions;
|
||||
|
||||
controller.set(
|
||||
"canSkipCrop",
|
||||
!control.mustBeCropped(
|
||||
flexWidth,
|
||||
flexHeight,
|
||||
xInit,
|
||||
yInit,
|
||||
realWidth,
|
||||
realHeight
|
||||
)
|
||||
);
|
||||
|
||||
if (xImg / yImg > ratio) {
|
||||
yInit = yImg;
|
||||
xInit = yInit * ratio;
|
||||
} else {
|
||||
xInit = xImg;
|
||||
yInit = xInit / ratio;
|
||||
}
|
||||
|
||||
x1 = (xImg - xInit) / 2;
|
||||
y1 = (yImg - yInit) / 2;
|
||||
|
||||
imgSelectOptions = {
|
||||
handles: true,
|
||||
keys: true,
|
||||
instance: true,
|
||||
persistent: true,
|
||||
imageWidth: realWidth,
|
||||
imageHeight: realHeight,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
x2: xInit + x1,
|
||||
y2: yInit + y1,
|
||||
};
|
||||
|
||||
if (false === flexHeight && false === flexWidth) {
|
||||
imgSelectOptions.aspectRatio = xInit + ":" + yInit;
|
||||
}
|
||||
if (false === flexHeight) {
|
||||
imgSelectOptions.maxHeight = yInit;
|
||||
}
|
||||
if (false === flexWidth) {
|
||||
imgSelectOptions.maxWidth = xInit;
|
||||
}
|
||||
|
||||
return imgSelectOptions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return whether the image must be cropped, based on required dimensions.
|
||||
*
|
||||
* @param {bool} flexW - The flex-width.
|
||||
* @param {bool} flexH - The flex-height.
|
||||
* @param {int} dstW - Initial point distance in the X axis.
|
||||
* @param {int} dstH - Initial point distance in the Y axis.
|
||||
* @param {int} imgW - Width.
|
||||
* @param {int} imgH - Height.
|
||||
* @returns {bool} - Whether the image must be cropped or not based on required dimensions.
|
||||
*/
|
||||
mustBeCropped: function (flexW, flexH, dstW, dstH, imgW, imgH) {
|
||||
return !(
|
||||
(true === flexW && true === flexH) ||
|
||||
(true === flexW && dstH === imgH) ||
|
||||
(true === flexH && dstW === imgW) ||
|
||||
(dstW === imgW && dstH === imgH) ||
|
||||
imgW <= dstW
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* If cropping was skipped, apply the image data directly to the setting.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onSkippedCrop: function () {
|
||||
var attachment = this.frame.state().get("selection").first().toJSON();
|
||||
this.setImageInRepeaterField(attachment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the setting and re-renders the control UI.
|
||||
*
|
||||
* @param {object} attachment - The attachment object.
|
||||
* @returns {void}
|
||||
*/
|
||||
setImageInRepeaterField: function (attachment) {
|
||||
var $targetDiv = this.$thisButton.closest(
|
||||
".repeater-field-image,.repeater-field-cropped_image"
|
||||
);
|
||||
|
||||
$targetDiv
|
||||
.find(".kirki-image-attachment")
|
||||
.html('<img src="' + attachment.url + '">')
|
||||
.hide()
|
||||
.slideDown("slow");
|
||||
|
||||
$targetDiv.find(".hidden-field").val(attachment.id);
|
||||
this.$thisButton.text(this.$thisButton.data("alt-label"));
|
||||
$targetDiv.find(".remove-button").show();
|
||||
|
||||
//This will activate the save button
|
||||
$targetDiv.find("input, textarea, select").trigger("change");
|
||||
this.frame.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the setting and re-renders the control UI.
|
||||
*
|
||||
* @param {object} attachment - The attachment object.
|
||||
* @returns {void}
|
||||
*/
|
||||
setFileInRepeaterField: function (attachment) {
|
||||
var $targetDiv = this.$thisButton.closest(".repeater-field-upload");
|
||||
|
||||
$targetDiv
|
||||
.find(".kirki-file-attachment")
|
||||
.html(
|
||||
'<span class="file"><span class="dashicons dashicons-media-default"></span> ' +
|
||||
attachment.filename +
|
||||
"</span>"
|
||||
)
|
||||
.hide()
|
||||
.slideDown("slow");
|
||||
|
||||
$targetDiv.find(".hidden-field").val(attachment.id);
|
||||
this.$thisButton.text(this.$thisButton.data("alt-label"));
|
||||
$targetDiv.find(".upload-button").show();
|
||||
$targetDiv.find(".remove-button").show();
|
||||
|
||||
//This will activate the save button
|
||||
$targetDiv.find("input, textarea, select").trigger("change");
|
||||
this.frame.close();
|
||||
},
|
||||
|
||||
getMimeType: function () {
|
||||
// We get the field id from which this was called
|
||||
var currentFieldId = this.$thisButton
|
||||
.siblings("input.hidden-field")
|
||||
.attr("data-field");
|
||||
|
||||
// Make sure we got it
|
||||
if (_.isString(currentFieldId) && "" !== currentFieldId) {
|
||||
// Make fields is defined and only do the hack for cropped_image
|
||||
if (
|
||||
_.isObject(this.params.fields[currentFieldId]) &&
|
||||
"upload" === this.params.fields[currentFieldId].type
|
||||
) {
|
||||
// If the attribute exists in the field
|
||||
if (!_.isUndefined(this.params.fields[currentFieldId].mime_type)) {
|
||||
// Set the attribute in the main object
|
||||
return this.params.fields[currentFieldId].mime_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "image";
|
||||
},
|
||||
|
||||
removeImage: function (event) {
|
||||
var $targetDiv, $uploadButton;
|
||||
|
||||
if (wp.customize.utils.isKeydownButNotEnterEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$targetDiv = this.$thisButton.closest(
|
||||
".repeater-field-image,.repeater-field-cropped_image,.repeater-field-upload"
|
||||
);
|
||||
$uploadButton = $targetDiv.find(".upload-button");
|
||||
|
||||
$targetDiv.find(".kirki-image-attachment").slideUp("fast", function () {
|
||||
jQuery(this).show().html(jQuery(this).data("placeholder"));
|
||||
});
|
||||
$targetDiv.find(".hidden-field").val("");
|
||||
$uploadButton.text($uploadButton.data("label"));
|
||||
this.$thisButton.hide();
|
||||
|
||||
$targetDiv.find("input, textarea, select").trigger("change");
|
||||
},
|
||||
|
||||
removeFile: function (event) {
|
||||
var $targetDiv, $uploadButton;
|
||||
|
||||
if (wp.customize.utils.isKeydownButNotEnterEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$targetDiv = this.$thisButton.closest(".repeater-field-upload");
|
||||
$uploadButton = $targetDiv.find(".upload-button");
|
||||
|
||||
$targetDiv.find(".kirki-file-attachment").slideUp("fast", function () {
|
||||
jQuery(this).show().html(jQuery(this).data("placeholder"));
|
||||
});
|
||||
$targetDiv.find(".hidden-field").val("");
|
||||
$uploadButton.text($uploadButton.data("label"));
|
||||
this.$thisButton.hide();
|
||||
|
||||
$targetDiv.find("input, textarea, select").trigger("change");
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current value of the setting
|
||||
*
|
||||
* @returns {Object} - Returns the value.
|
||||
*/
|
||||
getValue: function () {
|
||||
// The setting is saved in JSON
|
||||
return JSON.parse(decodeURI(this.setting.get()));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a new value for the setting
|
||||
*
|
||||
* @param {Object} newValue - The new value.
|
||||
* @param {bool} refresh - If we want to refresh the previewer or not
|
||||
* @param {bool} filtering - If we want to filter or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
setValue: function (newValue, refresh, filtering) {
|
||||
// We need to filter the values after the first load to remove data requrired for diplay but that we don't want to save in DB
|
||||
var filteredValue = newValue,
|
||||
filter = [];
|
||||
|
||||
if (filtering) {
|
||||
jQuery.each(this.params.fields, function (index, value) {
|
||||
if (
|
||||
"image" === value.type ||
|
||||
"cropped_image" === value.type ||
|
||||
"upload" === value.type
|
||||
) {
|
||||
filter.push(index);
|
||||
}
|
||||
});
|
||||
jQuery.each(newValue, function (index, value) {
|
||||
jQuery.each(filter, function (ind, field) {
|
||||
if (!_.isUndefined(value[field]) && !_.isUndefined(value[field].id)) {
|
||||
filteredValue[index][field] = value[field].id;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.setting.set(encodeURI(JSON.stringify(filteredValue)));
|
||||
|
||||
if (refresh) {
|
||||
// Trigger the change event on the hidden field so
|
||||
// previewer refresh the website on Customizer
|
||||
this.settingField.trigger("change");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new row to repeater settings based on the structure.
|
||||
*
|
||||
* @param {Object} data - (Optional) Object of field => value pairs (undefined if you want to get the default values)
|
||||
* @returns {Object} - Returns the new row.
|
||||
*/
|
||||
addRow: function (data) {
|
||||
var control = this,
|
||||
template = control.repeaterTemplate(), // The template for the new row (defined on Kirki_Customize_Repeater_Control::render_content() ).
|
||||
settingValue = this.getValue(), // Get the current setting value.
|
||||
newRowSetting = {}, // Saves the new setting data.
|
||||
templateData, // Data to pass to the template
|
||||
newRow,
|
||||
i;
|
||||
|
||||
if (template) {
|
||||
// The control structure is going to define the new fields
|
||||
// We need to clone control.params.fields. Assigning it
|
||||
// ould result in a reference assignment.
|
||||
templateData = jQuery.extend(true, {}, control.params.fields);
|
||||
|
||||
// But if we have passed data, we'll use the data values instead
|
||||
if (data) {
|
||||
for (i in data) {
|
||||
if (data.hasOwnProperty(i) && templateData.hasOwnProperty(i)) {
|
||||
templateData[i].default = data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templateData.index = this.currentIndex;
|
||||
|
||||
// Append the template content
|
||||
template = template(templateData);
|
||||
|
||||
// Create a new row object and append the element
|
||||
newRow = new RepeaterRow(
|
||||
control.currentIndex,
|
||||
jQuery(template).appendTo(control.repeaterFieldsContainer),
|
||||
control.params.row_label,
|
||||
control
|
||||
);
|
||||
|
||||
newRow.container.on("row:remove", function (e, rowIndex) {
|
||||
control.deleteRow(rowIndex);
|
||||
});
|
||||
|
||||
newRow.container.on(
|
||||
"row:update",
|
||||
function (e, rowIndex, fieldName, element) {
|
||||
control.updateField.call(control, e, rowIndex, fieldName, element); // eslint-disable-line no-useless-call
|
||||
newRow.updateLabel();
|
||||
}
|
||||
);
|
||||
|
||||
// Add the row to rows collection
|
||||
this.rows[this.currentIndex] = newRow;
|
||||
|
||||
for (i in templateData) {
|
||||
if (templateData.hasOwnProperty(i)) {
|
||||
newRowSetting[i] = templateData[i].default;
|
||||
}
|
||||
}
|
||||
|
||||
settingValue[this.currentIndex] = newRowSetting;
|
||||
this.setValue(settingValue, true);
|
||||
|
||||
this.currentIndex++;
|
||||
|
||||
return newRow;
|
||||
}
|
||||
},
|
||||
|
||||
sort: function () {
|
||||
var control = this,
|
||||
$rows = this.repeaterFieldsContainer.find(".repeater-row"),
|
||||
newOrder = [],
|
||||
settings = control.getValue(),
|
||||
newRows = [],
|
||||
newSettings = [];
|
||||
|
||||
$rows.each(function (i, element) {
|
||||
newOrder.push(jQuery(element).data("row"));
|
||||
});
|
||||
|
||||
jQuery.each(newOrder, function (newPosition, oldPosition) {
|
||||
newRows[newPosition] = control.rows[oldPosition];
|
||||
newRows[newPosition].setRowIndex(newPosition);
|
||||
|
||||
newSettings[newPosition] = settings[oldPosition];
|
||||
});
|
||||
|
||||
control.rows = newRows;
|
||||
control.setValue(newSettings);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a row in the repeater setting
|
||||
*
|
||||
* @param {int} index - Position of the row in the complete Setting Array
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteRow: function (index) {
|
||||
var currentSettings = this.getValue(),
|
||||
row,
|
||||
prop;
|
||||
|
||||
if (currentSettings[index]) {
|
||||
// Find the row
|
||||
row = this.rows[index];
|
||||
if (row) {
|
||||
// Remove the row settings
|
||||
delete currentSettings[index];
|
||||
|
||||
// Remove the row from the rows collection
|
||||
delete this.rows[index];
|
||||
|
||||
// Update the new setting values
|
||||
this.setValue(currentSettings, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Remap the row numbers
|
||||
for (prop in this.rows) {
|
||||
if (this.rows.hasOwnProperty(prop) && this.rows[prop]) {
|
||||
this.rows[prop].updateLabel();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a single field inside a row.
|
||||
* Triggered when a field has changed
|
||||
*
|
||||
* @param {Object} e - Event Object
|
||||
* @param {int} rowIndex - The row's index as an integer.
|
||||
* @param {string} fieldId - The field ID.
|
||||
* @param {string|Object} element - The element's identifier, or jQuery Object of the element.
|
||||
* @returns {void}
|
||||
*/
|
||||
updateField: function (e, rowIndex, fieldId, element) {
|
||||
var type, row, currentSettings;
|
||||
|
||||
if (!this.rows[rowIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.params.fields[fieldId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
type = this.params.fields[fieldId].type;
|
||||
row = this.rows[rowIndex];
|
||||
currentSettings = this.getValue();
|
||||
|
||||
element = jQuery(element);
|
||||
|
||||
if (_.isUndefined(currentSettings[row.rowIndex][fieldId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("checkbox" === type) {
|
||||
currentSettings[row.rowIndex][fieldId] = element.is(":checked");
|
||||
} else {
|
||||
// Update the settings
|
||||
currentSettings[row.rowIndex][fieldId] = element.val();
|
||||
}
|
||||
this.setValue(currentSettings, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the color picker on color fields
|
||||
* Called after AddRow
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initColorPicker: function () {
|
||||
var control = this;
|
||||
var colorPicker = control.container.find(".kirki-classic-color-picker");
|
||||
var fieldId = colorPicker.data("field");
|
||||
var options = {};
|
||||
|
||||
// We check if the color palette parameter is defined.
|
||||
if (
|
||||
!_.isUndefined(fieldId) &&
|
||||
!_.isUndefined(control.params.fields[fieldId]) &&
|
||||
!_.isUndefined(control.params.fields[fieldId].palettes) &&
|
||||
_.isObject(control.params.fields[fieldId].palettes)
|
||||
) {
|
||||
options.palettes = control.params.fields[fieldId].palettes;
|
||||
}
|
||||
|
||||
// When the color picker value is changed we update the value of the field
|
||||
options.change = function (event, ui) {
|
||||
var currentPicker = jQuery(event.target);
|
||||
var row = currentPicker.closest(".repeater-row");
|
||||
var rowIndex = row.data("row");
|
||||
var currentSettings = control.getValue();
|
||||
var value = ui.color._alpha < 1 ? ui.color.to_s() : ui.color.toString();
|
||||
|
||||
currentSettings[rowIndex][currentPicker.data("field")] = value;
|
||||
control.setValue(currentSettings, true);
|
||||
|
||||
// By default if the alpha is 1, the input will be rgb.
|
||||
// We setTimeout to 50ms to prevent race value set.
|
||||
setTimeout(function() {
|
||||
event.target.value = value;
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// Init the color picker
|
||||
if (colorPicker.length && 0 !== colorPicker.length) {
|
||||
colorPicker.wpColorPicker(options);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the dropdown-pages field.
|
||||
* Called after AddRow
|
||||
*
|
||||
* @param {object} theNewRow the row that was added to the repeater
|
||||
* @param {object} data the data for the row if we're initializing a pre-existing row
|
||||
* @returns {void}
|
||||
*/
|
||||
initSelect: function (theNewRow, data) {
|
||||
var control = this,
|
||||
dropdown = theNewRow.container.find(".repeater-field select"),
|
||||
dataField;
|
||||
|
||||
if (0 === dropdown.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataField = dropdown.data("field");
|
||||
multiple = jQuery(dropdown).data("multiple");
|
||||
|
||||
data = data || {};
|
||||
data[dataField] = data[dataField] || "";
|
||||
|
||||
jQuery(dropdown).val(data[dataField] || jQuery(dropdown).val());
|
||||
|
||||
this.container.on("change", ".repeater-field select", function (event) {
|
||||
var currentDropdown = jQuery(event.target),
|
||||
row = currentDropdown.closest(".repeater-row"),
|
||||
rowIndex = row.data("row"),
|
||||
currentSettings = control.getValue();
|
||||
|
||||
currentSettings[rowIndex][currentDropdown.data("field")] =
|
||||
jQuery(this).val();
|
||||
control.setValue(currentSettings);
|
||||
});
|
||||
},
|
||||
});
|
210
functions/kirki/kirki-packages/control-repeater/src/control.scss
Normal file
210
functions/kirki/kirki-packages/control-repeater/src/control.scss
Normal file
|
@ -0,0 +1,210 @@
|
|||
.customize-control-repeater {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-fields .repeater-row {
|
||||
border: 1px solid #e5e5e5;
|
||||
margin-top: 0.5rem;
|
||||
background: #eee;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-fields .repeater-row.minimized {
|
||||
border: 1px solid #dfdfdf;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-fields .repeater-row.minimized:hover {
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.customize-control-repeater
|
||||
.repeater-fields
|
||||
.repeater-row.minimized
|
||||
.repeater-row-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-fields .repeater-row label {
|
||||
margin-bottom: 12px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.customize-control-repeater
|
||||
.repeater-fields
|
||||
.repeater-row
|
||||
.repeater-field.repeater-field- {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customize-control-repeater
|
||||
.repeater-fields
|
||||
.repeater-row
|
||||
.repeater-field.repeater-field-radio-image
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customize-control-repeater
|
||||
.repeater-fields
|
||||
.repeater-row
|
||||
.repeater-field.repeater-field-radio-image
|
||||
input
|
||||
img {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.customize-control-repeater
|
||||
.repeater-fields
|
||||
.repeater-row
|
||||
.repeater-field.repeater-field-radio-image
|
||||
input:checked
|
||||
+ label
|
||||
img {
|
||||
-webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25);
|
||||
border: 1px solid #3498db;
|
||||
}
|
||||
|
||||
.customize-control-repeater
|
||||
.repeater-fields
|
||||
.repeater-row
|
||||
.repeater-field:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.customize-control-repeater button.repeater-add {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-content {
|
||||
padding: 10px 15px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field {
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
clear: both;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field .customize-control-title {
|
||||
font-size: 13px;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field .customize-control-description {
|
||||
font-size: 13px;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field.repeater-field-hidden {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field-select select {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field-checkbox label {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field-checkbox input {
|
||||
line-height: 28px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-field-textarea textarea {
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-header {
|
||||
background: white;
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
position: relative;
|
||||
padding: 10px 15px;
|
||||
height: auto;
|
||||
min-height: 20px;
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-header:hover {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-header .dashicons {
|
||||
font-size: 18px;
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 2px;
|
||||
color: #a0a5aa;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
display: block;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-remove {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-row-remove:hover {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.customize-control-repeater .repeater-minimize {
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.customize-control-repeater .remove-button,
|
||||
.customize-control-repeater .upload-button {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.kirki-image-attachment {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.kirki-image-attachment img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.kirki-file-attachment {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.kirki-file-attachment .file {
|
||||
display: block;
|
||||
padding: 10px 5px;
|
||||
border: 1px dotted #c3c3c3;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.limit {
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.limit.highlight {
|
||||
background: #d32f2f;
|
||||
color: #fff;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue