Skip to content
Open
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
703 changes: 703 additions & 0 deletions projects/plugins/jetpack/_inc/client/modules-admin/index.tsx

Large diffs are not rendered by default.

206 changes: 206 additions & 0 deletions projects/plugins/jetpack/_inc/client/modules-admin/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// Modules admin page styles. Scope the shared jetpack-admin-page-layout mixin
// to the wp-admin body class for `admin.php?page=jetpack_modules`. WP emits
// `admin_page_jetpack_modules` for this screen (the submenu is registered
// against an empty parent, so it resolves to `admin_page_*` — see
// class.jetpack-settings-page.php).

@use "@automattic/jetpack-base-styles/admin-page-layout" as *;

body.admin_page_jetpack_modules {

@include jetpack-admin-page-layout;
}

$jp-modules-admin-bg-active: #f6f7f7;
$jp-modules-admin-border: #dcdcde;
$jp-modules-admin-muted: #50575e;

.jp-modules-admin {
padding: 24px;

&__layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 280px;
gap: 24px;
align-items: start;

@media (max-width: 960px) {
grid-template-columns: 1fr;
}
}

&__main {
display: flex;
flex-direction: column;
gap: 16px;
}

&__bulk-toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: #fff;
border: 1px solid $jp-modules-admin-border;
border-radius: 4px;
}

&__bulk-master {
flex: 0 0 auto;
margin: 0 4px;
}

&__bulk-select {
flex: 0 0 auto;
min-width: 160px;

// Suppress the SelectControl margin-bottom default.
.components-base-control__field {
margin-bottom: 0;
}
}

&__bulk-count {
color: $jp-modules-admin-muted;
font-size: 12px;
margin-left: auto;
}

&__list {
background: #fff;
border: 1px solid $jp-modules-admin-border;
border-radius: 4px;
overflow: hidden;
}

&__row {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
padding: 12px 16px;
border-bottom: 1px solid $jp-modules-admin-border;

&:last-child {
border-bottom: none;
}

&.is-active {
background: $jp-modules-admin-bg-active;
}

&.is-unavailable {
opacity: 0.7;
}
}

&__row-select {

// Reserve checkbox-width even for unavailable rows so names line up.
min-width: 16px;
}

&__row-name {
font-weight: 500;
color: inherit;
text-decoration: none;

&:hover,
&:focus-visible {
color: var(--wp-admin-theme-color, #2271b1);
text-decoration: underline;
}
}

&__row-actions {
display: flex;
align-items: center;
gap: 12px;

// Inherit the muted color for the server-rendered Configure link.
.jp-modules-admin__row-configure a {
color: $jp-modules-admin-muted;
text-decoration: none;

&:hover {
text-decoration: underline;
}
}
}

&__row-unavailable {
color: $jp-modules-admin-muted;
font-size: 12px;
font-style: italic;
}

&__empty {
padding: 32px;
text-align: center;
color: $jp-modules-admin-muted;
}

&__sidebar {
display: flex;
flex-direction: column;
gap: 24px;
position: sticky;
top: 16px;
}

&__search {
// SearchControl ships its own borders; just ensure it stretches.
width: 100%;
}

&__tags {

.jp-modules-admin__tags-label {
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
color: $jp-modules-admin-muted;
margin-bottom: 8px;
}

ul {
list-style: none;
margin: 0;
padding: 0;
}

li {
margin: 0;
}
}

&__tag {
appearance: none;
background: none;
border: none;
padding: 4px 0;
font: inherit;
color: var(--wp-admin-theme-color, #2271b1);
cursor: pointer;
text-align: left;
text-decoration: underline;

&:hover {
text-decoration: none;
}

&.is-selected {
font-weight: 600;
color: inherit;
text-decoration: none;
}
}

&__tag-count {
color: $jp-modules-admin-muted;
text-decoration: none;
font-weight: 400;
margin-left: 4px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,17 @@ public function render() {
return;
}

// Check if we are looking at the main dashboard.
if ( isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View logic.
// Pages whose React tree provides its own header/footer chrome (via
// `<AdminPage>`) skip the legacy `wrap_ui()` wrapper to avoid stacking
// two mastheads and two footers around the same content.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View logic.
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
if ( in_array( $page, array( 'jetpack', 'jetpack_modules' ), true ) ) {
$this->page_render();
return;
}

$args = array();

// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['page'] ) && 'jetpack_modules' === $_GET['page'] ) {
$args['is-wide'] = true;
}

self::wrap_ui( array( $this, 'page_render' ), $args );
self::wrap_ui( array( $this, 'page_render' ), array() );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,22 @@ public function get_page_hook() {
}

/**
* Renders the module list table where you can use bulk action or row
* actions to activate/deactivate and configure modules
* Render the page body.
*
* Emits a single mount point (`<div id="jp-modules-admin-root">`) for the
* React `modules-admin` bundle, plus the noscript and REST-disabled
* fallback notices. `Jetpack_Modules_List_Table`'s constructor enqueues
* the bundle and localizes the `jetpackModulesData` blob the React app
* reads on mount.
*
* @since $$next-version$$
*/
public function page_render() {
$list_table = new Jetpack_Modules_List_Table();
// `Jetpack_Modules_List_Table::__construct` enqueues the React bundle
// and localizes `jetpackModulesData`, so instantiate it for the side
// effect.
// @phan-suppress-next-line PhanNoopNew -- Constructor enqueues scripts.
new Jetpack_Modules_List_Table();

// We have static.html so let's continue trying to fetch the others.
$noscript_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-noscript-notice.html' ); //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, Not fetching a remote file.
Expand Down Expand Up @@ -82,92 +93,7 @@ public function page_render() {
}
echo $noscript_notice; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>

<div class="jetpack-module-list">
<div class="wrap">
<div class="manage-left jp-static-block">
<table class="table table-bordered fixed-top jetpack-modules">
<thead>
<tr>
<th class="check-column"><input type="checkbox" class="checkall"></th>
<th colspan="2">
<?php $list_table->unprotected_display_tablenav( 'top' ); ?>
<span class="filter-search">
<button type="button" class="button">Filter</button>
</span>
</th>
</tr>
</thead>
</table>
<form class="jetpack-modules-list-table-form" onsubmit="return false;">
<table class="<?php echo esc_attr( implode( ' ', $list_table->get_table_classes() ) ); ?>">
<tbody id="the-list">
<?php $list_table->display_rows_or_placeholder(); ?>
</tbody>
</table>
</form>
</div>
<div class="manage-right">
<div class="bumper">
<form class="navbar-form" role="search">
<input type="hidden" name="page" value="jetpack_modules" />
<?php $list_table->search_box( __( 'Search', 'jetpack' ), 'srch-term' ); ?>
<p><?php esc_html_e( 'View', 'jetpack' ); ?></p>
<span class="dops-button-group button-group filter-active">
<button type="button" class="dops-button is-compact button
<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic.
if ( empty( $_GET['activated'] ) ) {
echo 'active';
}
?>
">
<?php esc_html_e( 'All', 'jetpack' ); ?></button>
<button type="button" class="dops-button button
<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic.
if ( ! empty( $_GET['activated'] ) && 'true' === $_GET['activated'] ) {
echo 'active';
}
?>
" data-filter-by="activated" data-filter-value="true"><?php esc_html_e( 'Active', 'jetpack' ); ?></button>
<button type="button" class="dops-button button
<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic.
if ( ! empty( $_GET['activated'] ) && 'false' === $_GET['activated'] ) {
echo 'active';
}
?>
" data-filter-by="activated" data-filter-value="false"><?php esc_html_e( 'Inactive', 'jetpack' ); ?></button>
</span>
<p><?php esc_html_e( 'Sort by', 'jetpack' ); ?></p>
<span class="dops-button-group button-group sort">
<button type="button" class="dops-button button
<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic.
if ( empty( $_GET['sort_by'] ) ) {
echo 'active';
}
?>
" data-sort-by="name"><?php esc_html_e( 'Alphabetical', 'jetpack' ); ?></button>
<button type="button" class="dops-button button
<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic.
if ( ! empty( $_GET['sort_by'] ) && 'introduced' === $_GET['sort_by'] ) {
echo 'active';
}
?>
" data-sort-by="introduced" data-sort-order="reverse"><?php esc_html_e( 'Newest', 'jetpack' ); ?></button>
<button type="button" class="dops-button button
<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic.
if ( ! empty( $_GET['sort_by'] ) && 'sort' === $_GET['sort_by'] ) {
echo 'active';
}
?>
" data-sort-by="sort"><?php esc_html_e( 'Popular', 'jetpack' ); ?></button>
</span>
<p><?php esc_html_e( 'Show', 'jetpack' ); ?></p>
<?php $list_table->views(); ?>
</form>
</div>
</div>
</div>
</div><!-- /.content -->
<div id="jp-modules-admin-root" class="jp-modules-admin-root"></div>
<?php

$tracking = new Tracking();
Expand Down
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/modules-page-bulk-actions
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: enhancement

Modules admin page: restore bulk activate / deactivate. Adds a per-row checkbox column, a "Select all" master checkbox scoped to the current view, and a Bulk actions select + Apply button above the list. Submits to the existing admin.php?page=jetpack&action=bulk-(activate|deactivate) handler with the bulk nonce; the server redirects back so module state refreshes naturally.
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/modules-page-name-link
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: enhancement

Modules admin page: render the module name as a link to its `learn_more_button` URL (the module's docs / product / wp-admin page), opening in a new tab — matching trunk's Backbone behavior. Modules without a `learn_more_button` keep the static name label.
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/modules-page-react-rewrite
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Modules admin page (`admin.php?page=jetpack_modules`): rewrite the legacy Backbone list-table view as a React tree wrapped in <AdminPage>, using the shared jetpack-admin-page-layout mixin. Activate/Deactivate buttons become <ToggleControl>; segmented filters use <ToggleGroupControl>; Dashboard/Settings move to the AdminPage actions slot. Backbone sources are unenqueued but left in place for a single-commit revert.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: enhancement

Modules admin page: persist search/filter/sort/tag in the URL via history.replaceState so a refresh or shared link restores filter state. Defaults are dropped from the URL to keep clean URLs clean.
Loading
Loading