Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 122 additions & 1 deletion assets/src/js/admin/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ document.addEventListener('DOMContentLoaded', () => {
* event.
*/
document.addEventListener('click', this.toggleOption);
/**
* Select All/None toggles.
*/
document.addEventListener('click', this.bulkToggle);

if (this.stepElems.length > 0) {
for (let i = 0; i < this.stepElems.length; i++) {
Expand All @@ -86,7 +90,7 @@ document.addEventListener('DOMContentLoaded', () => {
*/
toggleOption: async function (e) {
/**
* Make sure event target is a toggle.
* Make sure the event target is a toggle.
*/
if (e.target.classList === null || !e.target.classList.contains('plausible-analytics-toggle')) {
return;
Expand Down Expand Up @@ -123,6 +127,9 @@ document.addEventListener('DOMContentLoaded', () => {
plausible.toggleSection(button.value.replace('-', '_'));
}

const container = button.closest('.plausible-analytics-section');
plausible.syncBulkToggle(container);

const form = new FormData();
form.append('action', 'plausible_analytics_toggle_option');
form.append('option_name', button.name);
Expand All @@ -141,6 +148,120 @@ document.addEventListener('DOMContentLoaded', () => {
plausible.maybeDisableOptions(data.capabilities);
},

/**
* Toggles all underlying toggles when the Select All/None toggle is clicked.
*
* @param e
* @returns {Promise<void>}
*/
bulkToggle: async function (e) {
/**
* Make sure the event target is a bulk toggle.
*/
if (e.target.classList === null || !e.target.classList.contains('plausible-analytics-bulk-toggle')) {
return;
}

const button = e.target.closest('button');
const checked = button.dataset.status !== 'on';
const container = button.closest('.plausible-analytics-section');
const toggles = container.querySelectorAll('button.plausible-analytics-toggle');
const options = [];

/**
* Trigger animations for each toggle.
*/
toggles.forEach(function (toggle) {
const span = toggle.querySelector('span');

if (checked) {
toggle.classList.replace('bg-gray-200', 'bg-indigo-600');
span.classList.replace('translate-x-0', 'translate-x-5');
toggle.dataset.status = 'on';
} else {
toggle.classList.replace('bg-indigo-600', 'bg-gray-200');
span.classList.replace('translate-x-5', 'translate-x-0');
toggle.dataset.status = 'off';
}

options.push({
name: toggle.name,
value: toggle.value,
status: checked ? 'on' : '',
});
});

/**
* Toggle collapsable sections.
*/
toggles.forEach(function (toggle) {
if (toggle.dataset.addtlOpts !== '1') {
return;
}

const sectionName = toggle.value.replace('-', '_');
const section = document.getElementById(sectionName + '_content');

if (section === null) {
return;
}

const isHidden = section.classList.contains('hidden');

if (checked && isHidden) {
plausible.toggleSection(sectionName);
} else if (!checked && !isHidden) {
plausible.toggleSection(sectionName);
}
});

button.dataset.status = checked ? 'on' : 'off';

const bulkSpan = button.querySelector('span');

if (checked) {
button.classList.replace('bg-gray-200', 'bg-indigo-600');
bulkSpan.classList.replace('translate-x-0', 'translate-x-5');
} else {
button.classList.replace('bg-indigo-600', 'bg-gray-200');
bulkSpan.classList.replace('translate-x-5', 'translate-x-0');
}

const form = new FormData();
form.append('action', 'plausible_analytics_bulk_toggle');
form.append('options', JSON.stringify(options));
form.append('_nonce', plausible.nonce);

await plausible.ajax(form);
},

/**
* Sets the initial state of the Select All toggle.
*
* @param container
*/
syncBulkToggle: function (container) {
const bulkToggle = container.querySelector('.plausible-analytics-bulk-toggle');

if (bulkToggle === null) {
return;
}

const toggles = container.querySelectorAll('button.plausible-analytics-toggle');
const allOn = Array.from(toggles).every(t => t.dataset.status === 'on');
const bulkSpan = bulkToggle.querySelector('span');

if (allOn) {
bulkToggle.classList.replace('bg-gray-200', 'bg-indigo-600');
bulkSpan.classList.replace('translate-x-0', 'translate-x-5');
bulkToggle.dataset.status = 'on';
} else {
bulkToggle.classList.replace('bg-indigo-600', 'bg-gray-200');
bulkSpan.classList.replace('translate-x-5', 'translate-x-0');
bulkToggle.dataset.status = 'off';
}
},

/**
* Adds an input node.
*
Expand Down
37 changes: 31 additions & 6 deletions src/Admin/Settings/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -597,32 +597,57 @@ public function render_group_field( array $group, $hide_header = false ) {
/**
* if $fields contains more than one checkbox field type, this is a list, which is treated different in @see Ajax::toggle_option()
*/
$is_list = array_filter(
$toggles = array_filter(
$fields,
function ( $field ) {
return $field['type'] === 'checkbox';
}
);
$is_list = count( $toggles ) > 1;
$count = count( $fields );
$half = ceil( $count / 2 );
?>
<?php if ( $is_list ): ?>
<?php
$settings = Helpers::get_settings();
$option_name = $fields[ array_key_first( $fields ) ]['slug'];
$option_values = array_column(
array_filter( $fields, function ( $f ) {
return $f['type'] === 'checkbox';
} ),
'value'
);
$saved_values = $settings[ $option_name ] ?? [];
$all_checked = ! empty( $option_values ) && empty( array_diff( $option_values, (array) $saved_values ) );
?>
<div class="toggle-container flex items-center mt-4 mb-2 space-x-3">
<button class="plausible-analytics-bulk-toggle <?php echo $all_checked ? 'bg-indigo-600' : 'bg-gray-200'; ?> dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2
border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring"
type="checkbox" data-status="<?php echo $all_checked ? 'on' : 'off'; ?>">
<span
class="plausible-analytics-bulk-toggle <?php echo $all_checked ? 'translate-x-5' : 'translate-x-0'; ?> inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800
shadow transform transition-translate ease-in-out duration-200"></span>
</button>
<span class="ml-2 dark:text-gray-100 text-lg"><?php esc_html_e( 'Select all', 'plausible-analytics' ); ?></span>
</div>
<?php endif; ?>
<?php if ( $divide_in_columns && $count > 4 ) : ?>
<div class="grid grid-cols-1 md:grid-cols-2">
<div class="grid grid-cols-1 md:grid-cols-2 !mt-0">
<div>
<?php foreach ( array_slice( $fields, 0, $half ) as $field ) {
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, count( $is_list ) > 1 );
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, $is_list );
} ?>
</div>
<div>
<?php foreach ( array_slice( $fields, $half ) as $field ) {
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, count( $is_list ) > 1 );
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, $is_list );
} ?>
</div>
</div>
<?php else : ?>
<div>
<div class="!mt-0">
<?php foreach ( $fields as $field ) {
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, count( $is_list ) > 1 );
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, $is_list );
} ?>
</div>
<?php endif; ?>
Expand Down
50 changes: 46 additions & 4 deletions src/Ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private function init() {
add_action( 'wp_ajax_plausible_analytics_show_wizard', [ $this, 'show_wizard' ] );
add_action( 'wp_ajax_plausible_analytics_toggle_option', [ $this, 'toggle_option' ] );
add_action( 'wp_ajax_plausible_analytics_save_options', [ $this, 'save_options' ] );
add_action( 'wp_ajax_plausible_analytics_bulk_toggle', [ $this, 'bulk_toggle_options' ] );
}

/**
Expand Down Expand Up @@ -93,12 +94,12 @@ public function quit_wizard() {
* Clean variables using `sanitize_text_field`.
* Arrays are cleaned recursively. Non-scalar values are ignored.
*
* @param string|array $var Sanitize the variable.
*
* @return string|array
* @since 1.3.0
* @access public
*
* @param string|array $var Sanitize the variable.
*
* @return string|array
*/
private function clean( $var, $key = '' ) {
// If the variable is an array, recursively apply the function to each element of the array.
Expand Down Expand Up @@ -188,8 +189,8 @@ public function show_wizard() {
/**
* Save Admin Settings
*
* @return void
* @since 1.0.0
* @return void
*/
public function toggle_option() {
// Sanitize all the post data before using.
Expand Down Expand Up @@ -275,6 +276,47 @@ private function maybe_render_additional_message( $option_name, $option_value )
return $additional_message_html;
}

public function bulk_toggle_options() {
$post_data = $this->clean( $_POST );
$settings = Helpers::get_settings();

if ( ! current_user_can( 'manage_options' ) || wp_verify_nonce( $post_data['_nonce'], 'plausible_analytics_toggle_option' ) < 1 ) {
wp_send_json_error( __( 'Not allowed.', 'plausible-analytics' ), 403 );
}

$options = json_decode( $post_data['options'], true );

if ( empty( $options ) ) {
wp_send_json_error( __( 'No options found.', 'plausible-analytics' ), 400 );
}

foreach ( $options as $option ) {
$name = sanitize_text_field( $option['name'] );
$value = sanitize_text_field( $option['value'] );
$status = sanitize_text_field( $option['status'] );

if ( ! isset( $settings[ $name ] ) || ! is_array( $settings[ $name ] ) ) {
continue;
}

if ( $status === 'on' ) {
if ( ! in_array( $value, $settings[ $name ] ) ) {
$settings[ $name ][] = $value;
}
} else {
if ( ( $key = array_search( $value, $settings[ $name ] ) ) !== false ) {
unset( $settings[ $name ][ $key ] );
}
}
}

update_option( 'plausible_analytics_settings', $settings );

Messages::set_success( __( 'Settings saved.', 'plausible-analytics' ) );

wp_send_json_success( null, 200 );
}

/**
* Save Options
*
Expand Down
Loading