diff --git a/.github/workflows/build-blocks.yml b/.github/workflows/build-blocks.yml
new file mode 100644
index 000000000..9aad982e0
--- /dev/null
+++ b/.github/workflows/build-blocks.yml
@@ -0,0 +1,45 @@
+name: Build new theme and push to `build-blocks` branch.
+
+on:
+ push:
+ branches:
+ - trunk
+ paths:
+ - public_html/wp-content/themes/wporg-pattern-directory-2024/**
+ # Enable manually running action if necessary.
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ - name: Setup
+ uses: WordPress/wporg-repo-tools/.github/actions/setup@trunk
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Remove build artifacts
+ run: |
+ rm -rf public_html/wp-content/themes/wporg-pattern-directory-2024/node_modules
+
+ - name: Ignore .gitignore
+ run: |
+ git add public_html/wp-content/themes/wporg-pattern-directory-2024/* --force
+
+ - name: Append build number to version
+ run: |
+ current_version=$(grep -oP 'Version: \K[0-9]+\.[0-9]+\.[0-9]+' public_html/wp-content/themes/wporg-pattern-directory-2024/style.css)
+ new_version="${current_version}-${GITHUB_SHA::7}"
+ sed -i "s/Version: $current_version/Version: $new_version/" public_html/wp-content/themes/wporg-pattern-directory-2024/style.css
+
+ - name: Commit and push
+ # Using a specific hash here instead of a tagged version, for risk mitigation, since this action modifies our repo.
+ uses: actions-js/push@a52398fac807b0c1e5f1492c969b477c8560a0ba # 1.3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ branch: build-blocks
+ force: true
+ message: 'Build: ${{ github.sha }}'
diff --git a/.gitignore b/.gitignore
index 3e2b8bb7d..c42925c35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@
!/public_html/wp-content/plugins/pattern-directory
!/public_html/wp-content/plugins/pattern-translations
!/public_html/wp-content/themes/pattern-directory
+!/public_html/wp-content/themes/wporg-pattern-directory-2024
# Ignore built files inside our plugins & themes.
/public_html/wp-content/themes/pattern-directory/css/*.css
diff --git a/package.json b/package.json
index ba3b08898..cdbc19486 100644
--- a/package.json
+++ b/package.json
@@ -14,11 +14,12 @@
"build:creator": "yarn workspace wporg-pattern-creator build",
"build:directory": "yarn workspace wporg-pattern-directory build",
"build:theme:old": "yarn workspace wporg-pattern-directory-theme build",
- "build:theme": "yarn workspace wporg-pattern-directory-2022-theme build",
+ "build:theme": "yarn workspace wporg-pattern-directory-2024-theme build",
"start:creator": "yarn workspace wporg-pattern-creator start",
"start:directory": "yarn workspace wporg-pattern-directory start",
"start:theme:old": "yarn workspace wporg-pattern-directory-theme start",
- "start:theme": "yarn workspace wporg-pattern-directory-2022-theme start",
+ "start:theme": "yarn workspace wporg-pattern-directory-2024-theme start",
+ "setup:tools": "echo \"Not used.\"",
"create": "./bin/index.sh",
"wp-env": "wp-env",
"lint:php": "composer run lint",
@@ -28,6 +29,7 @@
"workspaces": [
"public_html/wp-content/plugins/pattern-creator",
"public_html/wp-content/plugins/pattern-directory",
- "public_html/wp-content/themes/pattern-directory"
+ "public_html/wp-content/themes/pattern-directory",
+ "public_html/wp-content/themes/wporg-pattern-directory-2024"
]
}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/functions.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/functions.php
new file mode 100644
index 000000000..272bdda5c
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/functions.php
@@ -0,0 +1,412 @@
+ $post_id,
+ 'post_status' => 'draft',
+ )
+ );
+ if ( $success ) {
+ // Reload the page without the action.
+ wp_safe_redirect( get_the_permalink() );
+ } else {
+ // Reload the page with an error flag.
+ $url = add_query_arg(
+ array(
+ 'error' => 'draft-failed'
+ ),
+ get_the_permalink()
+ );
+ wp_safe_redirect( $url );
+ }
+ }
+ } else if ( 'report' === $action ) {
+ if ( wp_verify_nonce( $nonce, 'report-' . $post_id ) && current_user_can( 'read' ) ) {
+ $success = wp_insert_post(
+ array(
+ 'post_type' => FLAG_POST_TYPE,
+ 'post_parent' => $post_id,
+ 'post_excerpt' => sanitize_text_field( $_POST['report-details'] ),
+ 'post_status' => PENDING_STATUS,
+ 'tax_input' => array(
+ 'wporg-pattern-flag-reason' => intval( $_POST['report-reason'] ),
+ ),
+ )
+ );
+ if ( $success ) {
+ $args = array(
+ 'success' => 'reported'
+ );
+ } else {
+ $args = array(
+ 'error' => 'report-failed'
+ );
+ }
+ } else {
+ $args = array(
+ 'error' => 'logged-out'
+ );
+ }
+
+ wp_safe_redirect( add_query_arg( $args, get_the_permalink() ) );
+ }
+}
+
+/**
+ * Add custom query parameters.
+ *
+ * @param array $query_vars
+ *
+ * @return array
+ */
+function add_patterns_query_vars( $query_vars ) {
+ $query_vars[] = 'curation';
+ $query_vars[] = 'status';
+ return $query_vars;
+}
+
+/**
+ * Update the query to show patters according to the "curation" &
+ * sort order filters.
+ *
+ * @param WP_Query $query The WP_Query instance (passed by reference).
+ */
+function modify_patterns_query( $query ) {
+ if ( is_admin() || ! $query->is_main_query() ) {
+ return;
+ }
+
+ // If `curation` is passed and either `core` or `community`, we should
+ // filter the result. If `curation=all`, no filtering is needed.
+ $curation = $query->get( 'curation' );
+ if ( $curation ) {
+ $tax_query = isset( $query->tax_query->queries ) ? $query->tax_query->queries : [];
+ if ( 'core' === $curation ) {
+ // Patterns with the core keyword.
+ $tax_query['core_keyword'] = array(
+ 'taxonomy' => 'wporg-pattern-keyword',
+ 'field' => 'slug',
+ 'terms' => [ 'core' ],
+ 'operator' => 'IN',
+ );
+ } else if ( 'community' === $curation ) {
+ // Patterns without the core keyword.
+ $tax_query['core_keyword'] = array(
+ 'taxonomy' => 'wporg-pattern-keyword',
+ 'field' => 'slug',
+ 'terms' => [ 'core' ],
+ 'operator' => 'NOT IN',
+ );
+ }
+ $query->set( 'tax_query', $tax_query );
+ }
+
+ if ( str_ends_with( $query->get( 'orderby' ), '_desc' ) ) {
+ $orderby = str_replace( '_desc', '', $query->get( 'orderby' ) );
+ $query->set( 'orderby', $orderby );
+ $query->set( 'order', 'desc' );
+ } else if ( str_ends_with( $query->get( 'orderby' ), '_asc' ) ) {
+ $orderby = str_replace( '_asc', '', $query->get( 'orderby' ) );
+ $query->set( 'orderby', $orderby );
+ $query->set( 'order', 'asc' );
+ }
+
+ if ( $query->get( 'orderby' ) === 'favorite_count' ) {
+ $query->set( 'orderby', 'meta_value_num' );
+ $query->set( 'meta_key', 'wporg-pattern-favorites' );
+ }
+
+ if ( ! $query->is_singular() ) {
+ $query->set( 'post_type', array( POST_TYPE ) );
+
+ // The `orderby_locale` meta_query will be transformed into a query orderby by Pattern_Post_Type\filter_orderby_locale().
+ $query->set( 'meta_query', array(
+ 'orderby_locale' => array(
+ 'key' => 'wpop_locale',
+ 'compare' => 'IN',
+ // Order in value determines result order
+ 'value' => array( get_locale(), 'en_US' ),
+ ),
+ ) );
+ }
+}
+
+/**
+ * Set up query customizations for the Query Loop block.
+ *
+ * @param array $query Array containing parameters for `WP_Query` as parsed by the block context.
+ * @param WP_Block $block Block instance.
+ * @param int $page Current query's page.
+ *
+ * @return array
+ */
+function modify_query_loop_block_query_vars( $query, $block, $page ) {
+ global $wp_query;
+
+ // Return early if this is a pattern view page.
+ if ( isset( $wp_query->query_vars['view'] ) ) {
+ return $query;
+ }
+
+ if ( ! isset( $query['posts_per_page'] ) ) {
+ $query['posts_per_page'] = 24;
+ }
+
+ if ( isset( $page ) && ! isset( $query['offset'] ) ) {
+ $query['paged'] = $page;
+ }
+
+ if ( isset( $block->context['query']['curation'] ) ) {
+ if ( 'core' === $block->context['query']['curation'] ) {
+ // Patterns with the core keyword.
+ $query['tax_query']['core_keyword'] = array(
+ 'taxonomy' => 'wporg-pattern-keyword',
+ 'field' => 'slug',
+ 'terms' => 'core',
+ 'operator' => 'IN',
+ );
+ } else if ( 'community' === $block->context['query']['curation'] ) {
+ // Patterns without the core keyword.
+ $query['tax_query']['core_keyword'] = array(
+ 'taxonomy' => 'wporg-pattern-keyword',
+ 'field' => 'slug',
+ 'terms' => [ 'core' ],
+ 'operator' => 'NOT IN',
+ );
+ }
+ }
+
+ if ( isset( $block->context['query']['orderBy'] ) && 'favorite_count' === $block->context['query']['orderBy'] ) {
+ $query['orderby'] = 'meta_value_num';
+ $query['meta_key'] = 'wporg-pattern-favorites';
+ }
+
+ // Query Loops on My Patterns & Favorites pages
+ if ( is_page( [ 'my-patterns', 'favorites' ] ) ) {
+ // Get these values from the global wp_query, they're passed via the URL.
+ if ( isset( $wp_query->query['pattern-categories'] ) ) {
+ if ( ! isset( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) ) {
+ $query['tax_query'] = array();
+ }
+ $query['tax_query'][] = array(
+ 'taxonomy' => 'wporg-pattern-category',
+ 'field' => 'slug',
+ 'terms' => $wp_query->query['pattern-categories'],
+ 'include_children' => false,
+ );
+ }
+
+ if ( isset( $wp_query->query['orderby'] ) ) {
+ if ( str_ends_with( $wp_query->query['orderby'], '_desc' ) ) {
+ $orderby = str_replace( '_desc', '', $wp_query->query['orderby'] );
+ $query['orderby'] = $orderby;
+ $query['order'] = 'desc';
+ } else if ( str_ends_with( $wp_query->query['orderby'], '_asc' ) ) {
+ $orderby = str_replace( '_asc', '', $wp_query->query['orderby'] );
+ $query['orderby'] = $orderby;
+ $query['order'] = 'asc';
+ }
+ }
+
+ if ( is_page( 'my-patterns' ) ) {
+ $user_id = get_current_user_id();
+ if ( $user_id ) {
+ $query['post_type'] = 'wporg-pattern';
+ $query['post_status'] = 'any';
+ $query['author'] = get_current_user_id();
+ } else {
+ $query['post__in'] = [ -1 ];
+ }
+
+ if ( isset( $wp_query->query['status'] ) ) {
+ $query['post_status'] = $wp_query->query['status'];
+ }
+ }
+
+ if ( is_page( 'favorites' ) ) {
+ $favorites = get_favorites();
+ if ( ! empty( $favorites ) ) {
+ $query['post__in'] = get_favorites();
+ } else {
+ $query['post__in'] = [ -1 ];
+ }
+ }
+ }
+
+ // The `orderby_locale` meta_query will be transformed into a query orderby by Pattern_Post_Type\filter_orderby_locale().
+ $query['meta_query'] = array(
+ 'orderby_locale' => array(
+ 'key' => 'wpop_locale',
+ 'compare' => 'IN',
+ // Order in value determines result order
+ 'value' => array( get_locale(), 'en_US' ),
+ ),
+ );
+
+ return $query;
+}
+
+/**
+ * Override Query Loop parameters if an `_id` property is found.
+ *
+ * This is a workaround to allow setting more complicated queries. For example,
+ * using the current author & excluding the current post.
+ *
+ * @param array $query Array containing parameters for `WP_Query` as parsed by the block context.
+ * @param WP_Block $block Block instance.
+ *
+ * @return array
+ */
+function custom_query_loop_by_id( $query, $block ) {
+ if ( ! isset( $block->context['query']['_id'] ) ) {
+ return $query;
+ }
+
+ $current_post = get_post();
+ if ( 'more-by-author' === $block->context['query']['_id'] && $current_post && $current_post->post_author ) {
+ $query['author'] = $current_post->post_author;
+ $query['post__not_in'] = [ $current_post->ID ];
+ $query['post_type'] = 'wporg-pattern';
+ }
+
+ if ( 'empty-favorites' === $block->context['query']['_id'] ) {
+ unset( $query['post__in'] );
+ $query['post_type'] = 'wporg-pattern';
+ $query['orderby'] = 'meta_value_num';
+ $query['meta_key'] = 'wporg-pattern-favorites';
+ }
+
+ return $query;
+}
+
+/**
+ * Get the preview URL for the current pattern.
+ *
+ * @param int|WP_Post $post Post ID or post object.
+ *
+ * @return string The pattern `view` URL.
+ */
+function get_pattern_preview_url( $post = 0 ) {
+ $view_url = add_query_arg( 'view', true, get_permalink( $post ) );
+ return apply_filters( 'wporg_pattern_preview_url', $view_url, $post );
+}
+
+/**
+ * Get the count of all patterns on the site (for the current locale).
+ *
+ * @return int
+ */
+function get_patterns_count() {
+ global $wpdb;
+ $locale = get_locale();
+
+ // Cache for an hour to avoid extra DB lookup.
+ $cache_key = 'wporg-patterns-count-' . $locale;
+ $ttl = HOUR_IN_SECONDS;
+
+ $count = get_transient( $cache_key );
+ if ( ! $count ) {
+ $sql = "SELECT COUNT(*) FROM $wpdb->posts
+ INNER JOIN $wpdb->postmeta
+ ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id
+ WHERE {$wpdb->posts}.post_status = 'publish'
+ AND {$wpdb->postmeta}.meta_key = 'wpop_locale'
+ AND {$wpdb->postmeta}.meta_value = '%s'";
+
+ $count = $wpdb->get_var( $wpdb->prepare( $sql, $locale ) );
+ set_transient( $cache_key, $count, $ttl );
+ }
+ return $count;
+}
+
+/**
+ * Checks whether the user has a pending flag for a specific pattern.
+ *
+ * @return bool
+ */
+function user_has_flagged_pattern() {
+ $args = array(
+ 'author' => get_current_user_id(),
+ 'post_parent' => get_the_ID(),
+ 'post_type' => FLAG_POST_TYPE,
+ 'post_status' => PENDING_STATUS,
+ );
+
+ $items = new \WP_Query( $args );
+
+ return $items->have_posts();
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/inc/block-config.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/inc/block-config.php
new file mode 100644
index 000000000..4e8c75b45
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/inc/block-config.php
@@ -0,0 +1,509 @@
+ __( 'Edit label', 'wporg-patterns' ),
+ 'uses_context' => [ 'postId' ],
+ 'get_value_callback' => function( $args, $block ) {
+ $post_id = $block->context['postId'];
+ /* translators: %s: Post title. Only visible to screen readers. */
+ return sprintf(
+ __( 'Edit "%s" ', 'wporg-patterns' ),
+ get_the_title( $post_id )
+ );
+ }
+ )
+ );
+
+ register_block_bindings_source(
+ 'wporg-pattern/edit-url',
+ array(
+ 'label' => __( 'Edit link', 'wporg-patterns' ),
+ 'uses_context' => [ 'postId' ],
+ 'get_value_callback' => function( $args, $block ) {
+ $post_id = $block->context['postId'];
+ return site_url( "pattern/$post_id/edit/" );
+ }
+ )
+ );
+}
+
+/**
+ * Get a list of the currently-applied filters.
+ *
+ * @param boolean $include_search Whether the result should include the search term.
+ *
+ * @return array
+ */
+function get_applied_filter_list( $include_search = true ) {
+ global $wp_query;
+ $terms = [];
+ $taxes = [
+ 'pattern-categories' => 'wporg-pattern-category',
+ ];
+ foreach ( $taxes as $query_var => $taxonomy ) {
+ if ( ! isset( $wp_query->query[ $query_var ] ) ) {
+ continue;
+ }
+ $values = (array) $wp_query->query[ $query_var ];
+ foreach ( $values as $value ) {
+ $key = ( 'cat' === $query_var ) ? 'id' : 'slug';
+ $term = get_term_by( $key, $value, $taxonomy );
+ if ( $term ) {
+ $terms[] = $term;
+ }
+ }
+ }
+ if ( $include_search && isset( $wp_query->query['s'] ) ) {
+ $terms[] = array( 'name' => $wp_query->query['s'] );
+ }
+ return $terms;
+}
+
+/**
+ * Get the destination for query-filter submission based on the current page.
+ *
+ * @return string
+ */
+function get_filter_action_url() {
+ global $wp;
+ if ( is_page( 'favorites' ) || is_page( 'my-patterns' ) || is_author() ) {
+ return home_url( $wp->request );
+ }
+ return home_url( '/archives/' );
+}
+
+/**
+ * Update the query total label to reflect "patterns" found.
+ *
+ * @param string $label The maybe-pluralized label to use, a result of `_n()`.
+ * @param int $found_posts The number of posts to use for determining pluralization.
+ * @return string Updated string with total placeholder.
+ */
+function update_query_total_label( $label, $found_posts ) {
+ if ( is_front_page() ) {
+ // Override the current query count, instead display the total number of patterns.
+ $count = get_patterns_count();
+
+ /* translators: %s: the result count. */
+ return sprintf( _n( '%s pattern', '%s patterns', $count, 'wporg' ), number_format_i18n( $count ) );
+ }
+ /* translators: %s: the result count. */
+ return _n( '%s pattern', '%s patterns', $found_posts, 'wporg' );
+}
+
+/**
+ * Get the list of categories for the filters.
+ *
+ * @param array $options The options for this filter.
+ * @return array New list of category options.
+ */
+function get_category_options( $options ) {
+ global $wp_query;
+
+ $args = array(
+ 'taxonomy' => 'wporg-pattern-category',
+ 'orderby' => 'name',
+ );
+ $categories = get_terms( $args );
+
+ $selected = isset( $wp_query->query['pattern-categories'] ) ? (array) $wp_query->query['pattern-categories'] : array();
+
+ $count = count( $selected );
+ $label = sprintf(
+ /* translators: The dropdown label for filtering, %s is the selected term count. */
+ _n( 'Categories %s ', 'Categories %s ', $count, 'wporg' ),
+ $count
+ );
+
+ return array(
+ 'label' => $label,
+ 'title' => __( 'Categories', 'wporg' ),
+ 'key' => 'pattern-categories',
+ 'action' => get_filter_action_url(),
+ 'options' => array_combine( wp_list_pluck( $categories, 'slug' ), wp_list_pluck( $categories, 'name' ) ),
+ 'selected' => $selected,
+ );
+}
+
+/**
+ * Provide a list of curation options.
+ *
+ * @param array $options The options for this filter.
+ * @return array New list of curation options.
+ */
+function get_curation_options( $options ) {
+ global $wp_query;
+ $current = strtolower( $wp_query->get( 'curation' ) );
+
+ $label = __( 'Filter by', 'wporg' );
+ switch ( $current ) {
+ case 'community':
+ $label = __( 'Filter by: Community', 'wporg' );
+ break;
+ case 'core':
+ $label = __( 'Filter by: Curated', 'wporg' );
+ break;
+ }
+
+ // Show the correct filters on the front page.
+ if ( is_front_page() ) {
+ $current = 'core';
+ $label = __( 'Filter by: Curated', 'wporg' );
+ }
+
+ return array(
+ 'label' => $label,
+ 'title' => __( 'Filter', 'wporg' ),
+ 'key' => 'curation',
+ 'action' => get_filter_action_url(),
+ 'options' => array(
+ 'community' => __( 'Community', 'wporg' ),
+ 'core' => __( 'Curated', 'wporg' ),
+ ),
+ 'selected' => [ $current ],
+ );
+}
+
+/**
+ * Provide a list of sort options.
+ *
+ * @param array $options The options for this filter.
+ * @return array New list of sort options.
+ */
+function get_sort_options( $options ) {
+ global $wp_query;
+ $orderby = strtolower( $wp_query->get( 'orderby' ) );
+ $order = strtolower( $wp_query->get( 'order' ) );
+ $sort = $orderby . '_' . $order;
+
+ $label = __( 'Sort', 'wporg' );
+ switch ( $sort ) {
+ case 'date_desc':
+ $label = __( 'Sort: Newest', 'wporg' );
+ break;
+ case 'date_asc':
+ $label = __( 'Sort: Oldest', 'wporg' );
+ break;
+ }
+
+ // Popular is a special case since it's not a true "order" value.
+ if ( 'meta_value_num' === $orderby && 'wporg-pattern-favorites' === $wp_query->get( 'meta_key' ) ) {
+ $label = __( 'Sort: Popular', 'wporg' );
+ }
+
+ // Show the correct filters on the front page.
+ if ( is_front_page() ) {
+ $sort = 'favorite_count_desc';
+ $label = __( 'Sort: Popular', 'wporg' );
+ }
+
+ $options = array(
+ 'date_desc' => __( 'Newest', 'wporg' ),
+ 'date_asc' => __( 'Oldest', 'wporg' ),
+ );
+
+ // These pages don't support sorting by favorite count.
+ if ( ! is_page( [ 'my-patterns', 'favorites' ] ) ) {
+ $options = array_merge( [ 'favorite_count_desc' => __( 'Popular', 'wporg' ) ], $options );
+ }
+
+ return array(
+ 'label' => $label,
+ 'title' => __( 'Sort', 'wporg' ),
+ 'key' => 'orderby',
+ 'action' => get_filter_action_url(),
+ 'options' => $options,
+ 'selected' => [ $sort ],
+ );
+}
+
+/**
+ * Provide a list of post status options.
+ *
+ * @param array $options The options for this filter.
+ * @return array New list of status options.
+ */
+function get_status_options( $options ) {
+ global $wp_query;
+ $selected = isset( $wp_query->query['status'] ) ? (array) $wp_query->query['status'] : array();
+
+ $count = count( $selected );
+ $label = sprintf(
+ /* translators: The dropdown label for filtering, %s is the selected term count. */
+ _n( 'Status %s ', 'Status %s ', $count, 'wporg' ),
+ $count
+ );
+
+ return array(
+ 'label' => $label,
+ 'title' => __( 'Status', 'wporg' ),
+ 'key' => 'status',
+ 'action' => get_filter_action_url(),
+ 'options' => array(
+ 'draft' => __( 'Draft', 'wporg' ),
+ 'pending' => __( 'Pending Review', 'wporg' ),
+ 'publish' => __( 'Published', 'wporg' ),
+ ),
+ 'selected' => $selected,
+ );
+}
+
+/**
+ * Add in the other existing filters as hidden inputs in the filter form.
+ *
+ * Enables combining filters by building up the correct URL on submit,
+ * for example patterns using a tag, a category, and matching a search term:
+ * ?tag[]=cuisine&cat[]=3&s=wordpress`
+ *
+ * @param string $key The key for the current filter.
+ */
+function inject_other_filters( $key ) {
+ global $wp_query;
+
+ // Multiple-select query parameters.
+ $query_vars = [ 'pattern-categories' ];
+ foreach ( $query_vars as $query_var ) {
+ if ( ! isset( $wp_query->query[ $query_var ] ) ) {
+ continue;
+ }
+ if ( $key === $query_var ) {
+ continue;
+ }
+ $values = (array) $wp_query->query[ $query_var ];
+ foreach ( $values as $value ) {
+ printf( ' ', esc_attr( $query_var ), esc_attr( $value ) );
+ }
+ }
+
+ // Single-select query parameters.
+ $query_vars = [ 'order', 'orderby', 'curation' ];
+ foreach ( $query_vars as $query_var ) {
+ if ( ! isset( $wp_query->query[ $query_var ] ) ) {
+ continue;
+ }
+ if ( $key === $query_var ) {
+ continue;
+ }
+ $values = (array) $wp_query->query[ $query_var ];
+ foreach ( $values as $value ) {
+ printf( ' ', esc_attr( $query_var ), esc_attr( $value ) );
+ }
+ }
+
+ if ( is_front_page() ) {
+ if ( $key !== 'curation' ) {
+ printf( ' ' );
+ }
+ if ( $key !== 'orderby' ) {
+ printf( ' ' );
+ }
+ }
+
+ // Pass through search query.
+ if ( isset( $wp_query->query['s'] ) ) {
+ printf( ' ', esc_attr( $wp_query->query['s'] ) );
+ }
+}
+
+/**
+ * Provide a list of local navigation menus.
+ */
+function add_site_navigation_menus( $menus ) {
+ $menu = array();
+
+ $menu[] = array(
+ 'label' => __( 'Favorites', 'wporg-patterns' ),
+ 'url' => '/favorites/',
+ );
+ if ( is_user_logged_in() ) {
+ $menu[] = array(
+ 'label' => __( 'My Patterns', 'wporg-patterns' ),
+ 'url' => '/my-patterns/',
+ );
+ }
+ $menu[] = array(
+ 'label' => __( 'New Pattern', 'wporg-patterns' ),
+ 'url' => '/new-pattern/',
+ );
+ return array(
+ 'main' => $menu,
+ );
+}
+
+/**
+ * Update the archive title for all filter views.
+ *
+ * @param string $block_content The block content.
+ * @param array $block The full block, including name and attributes.
+ * @param WP_Block $instance The block instance.
+ */
+function update_archive_title( $block_content, $block, $instance ) {
+ global $wp_query;
+ $attributes = $block['attrs'];
+
+ if ( isset( $attributes['type'] ) && 'filter' === $attributes['type'] ) {
+ // Skip output if there are no results. The `query-no-results` has an h1.
+ if ( ! $wp_query->found_posts ) {
+ return '';
+ }
+
+ $term_names = get_applied_filter_list();
+ if ( ! empty( $term_names ) ) {
+ $term_names = wp_list_pluck( $term_names, 'name' );
+ // translators: %s list of terms used for filtering.
+ $title = sprintf( __( 'Patterns: %s', 'wporg' ), implode( ', ', $term_names ) );
+ } else {
+ $author = isset( $wp_query->query['author_name'] ) ? get_user_by( 'slug', $wp_query->query['author_name'] ) : false;
+ if ( $author ) {
+ $title = sprintf( __( 'Author: %s', 'wporg' ), $author->display_name );
+ } else {
+ $title = __( 'All patterns', 'wporg' );
+ }
+ }
+
+ $tag_name = isset( $attributes['level'] ) ? 'h' . (int) $attributes['level'] : 'h1';
+ $align_class_name = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}";
+
+ // Required to prevent `block_to_render` from being null in `get_block_wrapper_attributes`.
+ $parent = WP_Block_Supports::$block_to_render;
+ WP_Block_Supports::$block_to_render = $block;
+ $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $align_class_name ) );
+ WP_Block_Supports::$block_to_render = $parent;
+
+ return sprintf(
+ '<%1$s %2$s>%3$s%1$s>',
+ $tag_name,
+ $wrapper_attributes,
+ $title
+ );
+ }
+ return $block_content;
+}
+
+/**
+ * Update the breadcrumbs to the current page.
+ */
+function update_site_breadcrumbs( $breadcrumbs ) {
+ global $wp_query;
+
+ // Build up the breadcrumbs from scratch.
+ $breadcrumbs = array(
+ array(
+ 'url' => home_url(),
+ 'title' => __( 'Home', 'wporg' ),
+ ),
+ );
+
+ if ( is_page() || is_single() ) {
+ $breadcrumbs[] = array(
+ 'url' => false,
+ 'title' => get_the_title(),
+ );
+ return $breadcrumbs;
+ }
+
+ if ( is_search() ) {
+ $breadcrumbs[] = array(
+ 'url' => home_url( '/archives/' ),
+ 'title' => __( 'All patterns', 'wporg' ),
+ );
+ $breadcrumbs[] = array(
+ 'url' => false,
+ 'title' => __( 'Search results', 'wporg' ),
+ );
+ return $breadcrumbs;
+ }
+
+ // `is_home` matches the "posts page", the All Patterns page.
+ // `is_archive` matches any core archive (category, date, etc).
+ if ( is_home() || is_archive() ) {
+ // Get the current applied filters (except search, handled above).
+ $term_names = get_applied_filter_list( false );
+ $author = isset( $wp_query->query['author_name'] ) ? get_user_by( 'slug', $wp_query->query['author_name'] ) : false;
+
+ $breadcrumbs[] = array(
+ 'url' => home_url( '/archives/' ),
+ 'title' => __( 'All patterns', 'wporg' ),
+ );
+
+ if ( $author ) {
+ $breadcrumbs[] = array(
+ 'url' => get_author_posts_url( $author->ID ),
+ 'title' => sprintf( __( 'Author: %s', 'wporg' ), $author->display_name ),
+ );
+ }
+
+ if ( $term_names ) {
+ $term_names = wp_list_pluck( $term_names, 'name' );
+ $breadcrumbs[] = array(
+ 'url' => false,
+ // translators: %s list of terms used for filtering.
+ 'title' => implode( ', ', $term_names ),
+ );
+ }
+ }
+
+ // Last item should be "current", no URL.
+ $breadcrumbs[count($breadcrumbs) - 1]['url'] = false;
+
+ return $breadcrumbs;
+}
+
+/**
+ * Update header template based on current query.
+ *
+ * @param array $parsed_block The block being rendered.
+ *
+ * @return array The updated block.
+ */
+function modify_pattern_include( $parsed_block ) {
+ if ( 'core/pattern' !== $parsed_block['blockName'] || empty( $parsed_block['attrs']['slug'] ) ) {
+ return $parsed_block;
+ }
+
+ if (
+ 'wporg-pattern-directory-2024/single-pattern' === $parsed_block['attrs']['slug'] &&
+ get_current_user_id() === get_the_author_meta( 'ID' )
+ ) {
+ $parsed_block['attrs']['slug'] = 'wporg-pattern-directory-2024/single-my-pattern';
+ }
+
+ if (
+ 'wporg-pattern-directory-2024/grid-mine' === $parsed_block['attrs']['slug'] &&
+ ! get_current_user_id()
+ ) {
+ $parsed_block['attrs']['slug'] = 'wporg-pattern-directory-2024/logged-out-patterns';
+ }
+
+ if (
+ 'wporg-pattern-directory-2024/grid-favorites' === $parsed_block['attrs']['slug'] &&
+ ! get_current_user_id()
+ ) {
+ $parsed_block['attrs']['slug'] = 'wporg-pattern-directory-2024/logged-out-favorites';
+ }
+
+ return $parsed_block;
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/inc/shortcodes.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/inc/shortcodes.php
new file mode 100644
index 000000000..a71c53cae
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/inc/shortcodes.php
@@ -0,0 +1,30 @@
+ 'draft',
+ '_wpnonce' => wp_create_nonce( 'draft-' . $post_id ),
+ ),
+ get_the_permalink()
+ );
+ }
+);
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/package.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/package.json
new file mode 100644
index 000000000..69e5159d5
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "wporg-pattern-directory-2024-theme",
+ "version": "1.0.0",
+ "description": "",
+ "author": "WordPress.org",
+ "license": "GPL-2.0-or-later",
+ "private": true,
+ "dependencies": {
+ "a11y-dialog": "^8.0.4"
+ },
+ "devDependencies": {
+ "@wordpress/scripts": "27.2.0"
+ },
+ "eslintConfig": {
+ "extends": "../../../../.eslintrc.js"
+ },
+ "stylelint": {
+ "extends": "../../../../.stylelintrc",
+ "ignoreFiles": [
+ "**/*.css",
+ "**/*.css.map"
+ ]
+ },
+ "scripts": {
+ "build": "wp-scripts build --experimental-modules",
+ "start": "wp-scripts start --experimental-modules",
+ "lint:js": "wp-scripts lint-js src",
+ "lint:css": "wp-scripts lint-style src/**/*.scss",
+ "format": "wp-scripts format src -- --config=../../../../.prettierrc.js"
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/parts/footer.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/parts/footer.html
new file mode 100644
index 000000000..564215d69
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/parts/footer.html
@@ -0,0 +1 @@
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/parts/header.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/parts/header.html
new file mode 100644
index 000000000..5029dae67
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/parts/header.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_footer.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_footer.php
new file mode 100644
index 000000000..2cbf01d62
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_footer.php
@@ -0,0 +1,51 @@
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-favorites.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-favorites.php
new file mode 100644
index 000000000..84dca26e1
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-favorites.php
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-front-page.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-front-page.php
new file mode 100644
index 000000000..637c8aa0c
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-front-page.php
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-mine.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-mine.php
new file mode 100644
index 000000000..f77e6bfb8
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid-mine.php
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid.php
new file mode 100644
index 000000000..774d1abf7
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_grid.php
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all patterns or try a different search. ', 'wporg' ) ),
+ esc_url( home_url( '/archives/' ) )
+ ); ?>
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_logged-out-favorites.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_logged-out-favorites.php
new file mode 100644
index 000000000..5bd4436c5
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_logged-out-favorites.php
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_logged-out-patterns.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_logged-out-patterns.php
new file mode 100644
index 000000000..09750f4cf
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/_logged-out-patterns.php
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/front-page-header.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/front-page-header.php
new file mode 100644
index 000000000..5d5cce926
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/front-page-header.php
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/single-my-pattern.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/single-my-pattern.php
new file mode 100644
index 000000000..28a36cb49
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/single-my-pattern.php
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/single-pattern.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/single-pattern.php
new file mode 100644
index 000000000..b07cb16d1
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/patterns/single-pattern.php
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/block.json
new file mode 100644
index 000000000..1ae4a7877
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/block.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/copy-button",
+ "version": "0.1.0",
+ "title": "Copy Button",
+ "category": "design",
+ "icon": "",
+ "description": "A button which will copy the current pattern code to the clipboard.",
+ "textdomain": "wporg",
+ "attributes": {
+ "variant": {
+ "type": "string",
+ "enum": [ "default", "small" ]
+ }
+ },
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId", "postType" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "viewScript": "file:./view.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/index.js
new file mode 100644
index 000000000..f56b8b425
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from '../../utils/dynamic-edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/index.php
new file mode 100644
index 000000000..0275cb5d3
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/index.php
@@ -0,0 +1,15 @@
+context['postId'];
+if ( ! $post_id ) {
+ return '';
+}
+
+$label = 'small' === $variant ? __( 'Copy', 'wporg-patterns' ) : __( 'Copy pattern', 'wporg-patterns' );
+$label_success = 'small' === $variant ? __( 'Copied', 'wporg-patterns' ) : __( 'Copied!', 'wporg-patterns' );
+
+$classes = [];
+if ( 'small' === $variant ) {
+ $classes[] = 'is-variant-small';
+ $classes[] = 'is-style-outline';
+}
+
+$post = get_post( $post_id );
+?>
+ implode( ' ', $classes ) ] ); // phpcs:ignore ?>>
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/style.scss
new file mode 100644
index 000000000..be3f20fdd
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/style.scss
@@ -0,0 +1,16 @@
+.wp-block-wporg-copy-button {
+ min-width: fit-content;
+
+ &.is-variant-small .wp-element-button {
+ --wp--custom--button--spacing--padding--top: calc(var(--wp--preset--spacing--10) / 2);
+ --wp--custom--button--spacing--padding--right: var(--wp--preset--spacing--10);
+ --wp--custom--button--spacing--padding--bottom: calc(var(--wp--preset--spacing--10) / 2);
+ --wp--custom--button--spacing--padding--left: var(--wp--preset--spacing--10);
+ font-size: var(--wp--preset--font-size--small);
+ font-weight: 400;
+ }
+
+ &:not(.is-variant-small) .wp-element-button {
+ min-width: 170px;
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/view.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/view.js
new file mode 100644
index 000000000..30589ab26
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/copy-button/view.js
@@ -0,0 +1,39 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { speak } from '@wordpress/a11y';
+
+/**
+ * Internal dependencies
+ */
+import copyToClipboard from '../../utils/copy-to-clipboard';
+
+const init = () => {
+ const containers = document.querySelectorAll( '.wp-block-wporg-copy-button' );
+ if ( containers ) {
+ containers.forEach( ( element ) => {
+ const button = element.querySelector( 'button' );
+ const input = element.querySelector( '.wp-block-wporg-copy-button__content' );
+ const content = JSON.parse( decodeURIComponent( input.value ) );
+
+ button.disabled = false;
+ button.onclick = async ( { target } ) => {
+ const success = copyToClipboard( content );
+
+ // Make sure we reset focus in case it was lost in the 'copy' command.
+ target.focus();
+
+ if ( success ) {
+ speak( __( 'Copied pattern to clipboard.', 'wporg-patterns' ) );
+ button.innerText = button.dataset.labelSuccess;
+ setTimeout( () => {
+ button.innerText = button.dataset.label;
+ }, 20000 );
+ }
+ };
+ } );
+ }
+};
+
+window.addEventListener( 'load', init );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/block.json
new file mode 100644
index 000000000..863143922
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/block.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/delete-button",
+ "version": "0.1.0",
+ "title": "Delete Button",
+ "category": "design",
+ "icon": "",
+ "description": "A button which will delete the current pattern (after an AYS).",
+ "textdomain": "wporg",
+ "attributes": {},
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId", "postType" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "viewScriptModule": "file:./view.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/index.js
new file mode 100644
index 000000000..f56b8b425
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from '../../utils/dynamic-edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/index.php
new file mode 100644
index 000000000..3bc319614
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/index.php
@@ -0,0 +1,15 @@
+context['postId'];
+if ( ! $post_id ) {
+ return;
+}
+
+// Check if the user has permissions.
+if ( ! current_user_can( 'edit_post', $post_id ) ) {
+ return;
+}
+
+// Manually enqueue this script, so that it's available for the interactivity view script.
+wp_enqueue_script( 'wp-api-fetch' );
+
+// Initial state to pass to Interactivity API.
+$init_state = [
+ 'postId' => $post_id,
+ 'message' => __( 'Are you sure you want to delete this pattern?', 'wporg-patterns' ),
+ 'redirectUrl' => home_url( '/my-patterns/' ),
+];
+$encoded_state = wp_json_encode( $init_state );
+
+?>
+ 'is-small is-style-outline' ] ); // phpcs:ignore ?>
+ data-wp-interactive="wporg/patterns/delete-button"
+ data-wp-context=""
+>
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/style.scss
new file mode 100644
index 000000000..0f226562d
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/style.scss
@@ -0,0 +1,11 @@
+.wp-block-wporg-delete-button {
+ --wp--custom--button--outline--color--text: #d63638;
+
+ --wp--custom--button--outline--hover--color--text: var(--wp--preset--color--white);
+ --wp--custom--button--outline--hover--color--background: #d63638;
+ --wp--custom--button--outline--hover--border--color: #d63638;
+
+ --wp--custom--button--outline--focus--border--color: #d63638;
+ --wp--custom--button--outline--focus--color--background: #d63638;
+ --wp--custom--button--outline--focus--color--text: var(--wp--preset--color--white);
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/view.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/view.js
new file mode 100644
index 000000000..34a293bc2
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/delete-button/view.js
@@ -0,0 +1,24 @@
+/**
+ * WordPress dependencies
+ */
+import { getContext, store } from '@wordpress/interactivity';
+
+store( 'wporg/patterns/delete-button', {
+ actions: {
+ *triggerDelete() {
+ const { postId, message, redirectUrl } = getContext();
+ const approved = yield window.confirm( message ); // eslint-disable-line no-alert
+ if ( approved ) {
+ try {
+ yield wp.apiFetch( {
+ path: `/wp/v2/wporg-pattern/${ postId }/`,
+ method: 'DELETE',
+ } );
+ } catch ( error ) {
+ return;
+ }
+ window.location = redirectUrl;
+ }
+ },
+ },
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/block.json
new file mode 100644
index 000000000..ba1db1065
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/block.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/favorite-button",
+ "version": "0.1.0",
+ "title": "Favorite Button",
+ "category": "design",
+ "icon": "",
+ "description": "A button showing the current count of favorites, which can toggle favoriting on the current pattern.",
+ "textdomain": "wporg",
+ "attributes": {
+ "variant": {
+ "type": "string",
+ "enum": [ "default", "small" ]
+ }
+ },
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId", "postType" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "viewScriptModule": "file:./view.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/index.js
new file mode 100644
index 000000000..f56b8b425
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from '../../utils/dynamic-edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/index.php
new file mode 100644
index 000000000..ece9c601e
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/index.php
@@ -0,0 +1,15 @@
+context['postId'];
+if ( ! $post_id ) {
+ return '';
+}
+
+$user_id = get_current_user_id();
+// Only the small variant should show if anon user.
+if ( ! $user_id && 'small' !== $variant ) {
+ return '';
+}
+
+// Manually enqueue this script, so that it's available for the interactivity view script.
+wp_enqueue_script( 'wp-api-fetch' );
+
+$is_favorite = is_favorite( $post_id, $user_id );
+$classes = [ 'is-style-text is-small' ];
+if ( 'small' === $variant ) {
+ $classes[] = 'is-variant-small';
+}
+$classes = implode( ' ', $classes );
+
+$tag_name = ! $user_id ? 'span' : 'button';
+$favorite_count = get_favorite_count( $post_id );
+
+$add_label = __( 'Add to favorites', 'wporg-patterns' );
+$remove_label = __( 'Remove from favorites', 'wporg-patterns' );
+$sr_label = sprintf(
+ _n(
+ 'Favorited %s time',
+ 'Favorited %s times',
+ $favorite_count,
+ 'wporg-patterns'
+ ),
+ $favorite_count
+);
+
+// Initial state to pass to Interactivity API.
+$init_state = [
+ 'postId' => $post_id,
+ 'isFavorite' => $is_favorite,
+ 'count' => $favorite_count,
+ 'label' => [
+ 'add' => $add_label,
+ 'remove' => $remove_label,
+ 'screenReader' => __( 'Favorited %s times', 'wporg-patterns' ),
+ ]
+];
+$encoded_state = wp_json_encode( $init_state );
+
+?>
+ $classes ] ); // phpcs:ignore ?>
+ data-wp-interactive="wporg/patterns/favorite-button"
+ data-wp-context=""
+ data-wp-class--is-favorite="context.isFavorite"
+>
+
+ <
+ class="wporg-favorite-button__button"
+ disabled="disabled"
+ data-wp-bind--disabled="!context.postId"
+ data-wp-on--click="actions.triggerAction"
+ >
+
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/style.scss
new file mode 100644
index 000000000..c48559020
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/style.scss
@@ -0,0 +1,54 @@
+:where(.wp-block-wporg-favorite-button) {
+ .wporg-favorite-button__button {
+ margin: 0;
+ padding: 0;
+ background: none;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
+ font-size: 14px;
+ }
+
+ > * {
+
+ /* Align children. */
+ display: flex !important;
+ align-items: center;
+ gap: calc(var(--wp--preset--spacing--10) / 2);
+ }
+
+ svg {
+ margin-top: 2px;
+ height: 24px;
+ width: 24px;
+ overflow: visible;
+
+ path {
+ fill: var(--wp--preset--color--charcoal-4);
+ }
+
+ &.is-star-empty {
+ display: block;
+ }
+
+ &.is-star-filled {
+ display: none;
+ }
+ }
+
+ &.is-favorite {
+ svg.is-star-empty {
+ display: none;
+ }
+
+ svg.is-star-filled {
+ display: block;
+ }
+ }
+
+ &:not(.is-variant-small) {
+ svg path {
+ fill: currentColor;
+ }
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/view.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/view.js
new file mode 100644
index 000000000..bbf0e996a
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/favorite-button/view.js
@@ -0,0 +1,47 @@
+/**
+ * WordPress dependencies
+ */
+import { getContext, store } from '@wordpress/interactivity';
+
+store( 'wporg/patterns/favorite-button', {
+ state: {
+ get labelScreenReader() {
+ const { label, count } = getContext();
+ return label.screenReader.replace( '%s', count );
+ },
+ get labelCount() {
+ const { count } = getContext();
+ return `(${ count })`;
+ },
+ get labelAction() {
+ const { label, isFavorite } = getContext();
+ return isFavorite ? label.remove : label.add;
+ },
+ },
+ actions: {
+ *triggerAction() {
+ const context = getContext();
+ if ( context.isFavorite ) {
+ try {
+ const newCount = yield wp.apiFetch( {
+ path: '/wporg/v1/pattern-favorites',
+ method: 'DELETE',
+ data: { id: context.postId },
+ } );
+ context.isFavorite = false;
+ context.count = newCount;
+ } catch ( error ) {}
+ } else {
+ try {
+ const newCount = yield wp.apiFetch( {
+ path: '/wporg/v1/pattern-favorites',
+ method: 'POST',
+ data: { id: context.postId },
+ } );
+ context.isFavorite = true;
+ context.count = newCount;
+ } catch ( error ) {}
+ }
+ },
+ },
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/block.json
new file mode 100644
index 000000000..8c98e9ff8
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/block.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/pattern-view-control",
+ "version": "0.1.0",
+ "title": "Pattern View Control",
+ "category": "design",
+ "icon": "",
+ "description": "A wrapper for the pattern preview to control the viewport size.",
+ "textdomain": "wporg",
+ "supports": {
+ "align": [ "wide", "full" ],
+ "html": false,
+ "spacing": {
+ "margin": true,
+ "padding": false,
+ "blockGap": false
+ }
+ },
+ "usesContext": [ "postId", "postType" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "viewScriptModule": "file:./view.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/index.js
new file mode 100644
index 000000000..0ffb8f2c6
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/index.js
@@ -0,0 +1,18 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * Internal dependencies
+ */
+import metadata from './block.json';
+import './style.scss';
+
+const Edit = () => Thumbnail
;
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/render.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/render.php
new file mode 100644
index 000000000..6cd4253fc
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/render.php
@@ -0,0 +1,82 @@
+context['postId'] ) ) {
+ return '';
+}
+
+$view_url = get_pattern_preview_url( $block->context['postId'] );
+
+// Initial state to pass to Interactivity API.
+$init_state = [
+ 'url' => $view_url,
+ 'previewWidth' => 1200,
+ 'previewHeight' => 200,
+];
+$encoded_state = wp_json_encode( $init_state );
+
+// Remove the nested context for child blocks, so that it uses this context.
+$p = new WP_HTML_Tag_Processor( $content );
+$p->next_tag( 'div' );
+$p->remove_attribute( 'data-wp-interactive' );
+$p->remove_attribute( 'data-wp-context' );
+$content = $p->get_updated_html();
+
+?>
+
+ data-wp-interactive="wporg/patterns/preview"
+ data-wp-context=""
+ data-wp-init="actions.handleOnResize"
+ data-wp-on-window--resize="actions.handleOnResize"
+>
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/style.scss
new file mode 100644
index 000000000..6d2ab62b4
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/style.scss
@@ -0,0 +1,40 @@
+.wp-block-wporg-pattern-view-control {
+ position: relative;
+ width: 100%;
+ --wp--custom--wporg-pattern-preview--border--width: clamp(4px, calc(1.33vw - 1.33px), 20px);
+ --wp--custom--wporg-pattern-preview--border--radius: clamp(4px, calc(1.33vw - 1.33px), 20px);
+}
+
+.wporg-pattern-view-control__controls {
+ margin-top: calc(var(--wp--preset--spacing--20) + var(--wp--custom--wporg-pattern-preview--border--width));
+ display: flex;
+ justify-content: center;
+ gap: var(--wp--preset--spacing--10);
+
+ .wp-element-button {
+ display: flex !important;
+ flex-direction: column;
+ align-items: center;
+
+ svg {
+ fill: currentColor !important;
+ }
+ }
+}
+
+.wp-block-wporg-pattern-view-control .wp-block-wporg-pattern-preview {
+ // The height is calculated based on the child iframe, `content-box` means
+ // this does not need to include the padding.
+ box-sizing: content-box;
+ border: none;
+ padding: var(--wp--custom--wporg-pattern-preview--border--width);
+
+ > iframe {
+ margin: 0 auto;
+ display: block;
+ border-radius: var(--wp--custom--wporg-pattern-preview--border--radius);
+ outline-color: var(--wp--custom--wporg-pattern-preview--border--color);
+ outline-style: solid;
+ outline-width: var(--wp--custom--wporg-pattern-preview--border--width);
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/view.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/view.js
new file mode 100644
index 000000000..60e86ba18
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/controls/view.js
@@ -0,0 +1,73 @@
+/**
+ * WordPress dependencies
+ */
+import { getContext, getElement, store } from '@wordpress/interactivity';
+
+const { actions, state } = store( 'wporg/patterns/preview', {
+ state: {
+ get scale() {
+ const { pageWidth, previewWidth } = getContext();
+ const scale = parseInt( pageWidth, 10 ) / previewWidth;
+ return scale > 1 ? 1 : scale;
+ },
+ get previewHeightCSS() {
+ return `${ getContext().previewHeight }px`;
+ },
+ get iframeWidthCSS() {
+ return `${ getContext().previewWidth }px`;
+ },
+ get iframeHeightCSS() {
+ return `${ getContext().previewHeight / state.scale }px`;
+ },
+ get transformCSS() {
+ return `scale(${ state.scale })`;
+ },
+ get isWidth1200() {
+ return 1200 === getContext().previewWidth;
+ },
+ get isWidth960() {
+ return 960 === getContext().previewWidth;
+ },
+ get isWidth600() {
+ return 600 === getContext().previewWidth;
+ },
+ get isWidth480() {
+ return 480 === getContext().previewWidth;
+ },
+ },
+ actions: {
+ onWidthChange() {
+ const { ref } = getElement();
+ const context = getContext();
+ context.previewWidth = parseInt( ref.dataset.width, 10 );
+ },
+ *onLoad() {
+ const { ref } = getElement();
+
+ yield new Promise( ( resolve ) => {
+ ref.addEventListener( 'load', () => resolve() );
+ } );
+
+ // iframe is loaded now, so we should adjust the height.
+ actions.updatePreviewHeight();
+ },
+ updatePreviewHeight() {
+ const context = getContext();
+ const { ref } = getElement();
+
+ // Need to "use" previewWidth so that `data-wp-watch` will re-run this action when it changes.
+ context.previewWidth; // eslint-disable-line no-unused-expressions
+
+ const iframeDoc = ref.contentDocument;
+ const height = iframeDoc.querySelector( '.entry-content' )?.clientHeight;
+ if ( height ) {
+ context.previewHeight = height * state.scale;
+ }
+ },
+ handleOnResize() {
+ const context = getContext();
+ const { ref } = getElement();
+ context.pageWidth = ref.clientWidth;
+ },
+ },
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/block.json
new file mode 100644
index 000000000..f5438f34d
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/block.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/pattern-preview",
+ "version": "0.1.0",
+ "title": "Pattern Preview",
+ "category": "design",
+ "icon": "",
+ "description": "Display a preview for the current pattern.",
+ "textdomain": "wporg",
+ "supports": {
+ "align": ["wide", "full"],
+ "html": false,
+ "spacing": {
+ "margin": true,
+ "padding": false,
+ "blockGap": false
+ }
+ },
+ "usesContext": [ "postId", "postType", "queryId" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "viewScriptModule": "wporg-pattern-view-control-view-script-module",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/edit.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/edit.js
new file mode 100644
index 000000000..32c6fbfd4
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/edit.js
@@ -0,0 +1,14 @@
+/**
+ * WordPress dependencies
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * The edit function describes the structure of your block in the context of the
+ * editor. This represents what the editor will render when the block is used.
+ *
+ * @return {Element} Element to render.
+ */
+export default function Edit() {
+ return Thumbnail
;
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/index.js
new file mode 100644
index 000000000..5d6da2ae2
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/render.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/render.php
new file mode 100644
index 000000000..5835620f8
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/render.php
@@ -0,0 +1,45 @@
+context['postId'] ) ) {
+ return '';
+}
+
+$view_url = get_pattern_preview_url( $block->context['postId'] );
+$viewport_width = get_post_meta( $block->context['postId'], 'wpop_viewport_width', true );
+
+if ( ! $viewport_width ) {
+ $viewport_width = 1200;
+}
+
+// Initial state to pass to Interactivity API.
+$init_state = [
+ 'url' => $view_url,
+ 'previewWidth' => $viewport_width,
+ 'previewHeight' => 200,
+];
+$encoded_state = wp_json_encode( $init_state );
+
+?>
+
+ data-wp-interactive="wporg/patterns/preview"
+ data-wp-context=""
+ data-wp-style--height="state.previewHeightCSS"
+ data-wp-init="actions.handleOnResize"
+ data-wp-on-window--resize="actions.handleOnResize"
+ tabIndex="-1"
+>
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/style.scss
new file mode 100644
index 000000000..8e15f13c1
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/frame/style.scss
@@ -0,0 +1,16 @@
+.wp-block-wporg-pattern-preview {
+ border-color: var(--wp--custom--wporg-pattern-thumbnail--border--color);
+ border-radius: var(--wp--custom--wporg-pattern-thumbnail--border--radius);
+ border-style: solid;
+ border-width: var(--wp--custom--wporg-pattern-thumbnail--border--width);
+ margin: 0;
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+
+ iframe {
+ border: none;
+ max-width: none;
+ pointer-events: none;
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/index.php
new file mode 100644
index 000000000..2991d445a
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-preview/index.php
@@ -0,0 +1,16 @@
+Thumbnail;
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/index.js
new file mode 100644
index 000000000..5d6da2ae2
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/index.php
new file mode 100644
index 000000000..c2c0f027b
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/index.php
@@ -0,0 +1,22 @@
+context['postId'] ) ) {
+ return '';
+}
+$post_id = $block->context['postId'];
+
+$view_url = get_pattern_preview_url( $post_id );
+$has_link = isset( $attributes['isLink'] ) && true == $attributes['isLink'];
+$is_lazyload = isset( $attributes['lazyLoad'] ) && true === $attributes['lazyLoad'];
+
+$viewport_width = get_post_meta( $post_id, 'wpop_viewport_width', true );
+
+if ( ! $viewport_width ) {
+ $viewport_width = 1200;
+}
+
+$cache_key = '20240223'; // To break out of cached image.
+
+$view_url = add_query_arg( 'v', $cache_key, $view_url );
+$url = add_query_arg(
+ array(
+ 'scale' => 2,
+ 'w' => 1100,
+ 'vpw' => $viewport_width,
+ 'vph' => 300, // Smaller than the vast majority of patterns to avoid whitespace.
+ 'screen_height' => 3600, // Max height of a screenshot.
+ ),
+ 'https://s0.wp.com/mshots/v1/' . urlencode( $view_url ),
+);
+
+// Initial state to pass to Interactivity API.
+$init_state = [
+ 'base64Image' => '',
+ 'src' => esc_url( $url ),
+ 'alt' => the_title_attribute( array( 'echo' => false ) ),
+];
+$encoded_state = wp_json_encode( $init_state );
+
+$classname = '';
+if ( $has_link ) {
+ $classname .= ' is-linked-image';
+}
+
+?>
+ $classname ) ); // phpcs:ignore ?>
+ data-wp-interactive="wporg/patterns/thumbnail"
+ data-wp-context=""
+ data-wp-init="callbacks.init"
+ data-wp-class--has-loaded="state.hasLoaded"
+ tabIndex="-1"
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/style.scss
new file mode 100644
index 000000000..bdee4771f
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/style.scss
@@ -0,0 +1,74 @@
+.wp-block-wporg-pattern-thumbnail {
+ border-color: var(--wp--custom--wporg-pattern-thumbnail--border--color);
+ border-radius: var(--wp--custom--wporg-pattern-thumbnail--border--radius);
+ border-style: solid;
+ border-width: var(--wp--custom--wporg-pattern-thumbnail--border--width);
+ margin: 0;
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+
+ &:where(.is-linked-image):hover,
+ &:where(.is-linked-image):focus-within {
+ --wp--custom--wporg-pattern-thumbnail--border--color: rgba(30, 30, 30, 0.25);
+ }
+
+ &:where(.is-linked-image):focus-within {
+ outline: 1.5px solid var(--wp--custom--link--color--text);
+ outline-offset: -1.5px;
+ }
+
+ img {
+ width: 100%;
+ vertical-align: middle;
+ }
+
+ &:not(.has-loaded) {
+ aspect-ratio: 21 / 9;
+ }
+
+ .wporg-pattern-thumbnail__loader {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+
+ img,
+ span {
+ display: none;
+ }
+
+ &::after {
+ content: "";
+ display: inline-block;
+ box-sizing: border-box;
+ height: 16px;
+ width: 16px;
+ border: 1.5px solid;
+ border-color:
+ var(--wp--custom--wporg-pattern-thumbnail--border--color)
+ var(--wp--custom--wporg-pattern-thumbnail--border--color)
+ var(--wp--custom--link--color--text);
+ border-radius: 50%;
+ animation: rotate-360 1.4s linear infinite;
+ }
+ }
+
+ .wporg-pattern-thumbnail__error {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ aspect-ratio: 21 / 9;
+ }
+}
+
+@keyframes rotate-360 {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/view.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/view.js
new file mode 100644
index 000000000..4711b9483
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/pattern-thumbnail/view.js
@@ -0,0 +1,88 @@
+/* global FileReader */
+/**
+ * WordPress dependencies
+ */
+import { getContext, store } from '@wordpress/interactivity';
+
+/**
+ * Module constants
+ */
+const MAX_ATTEMPTS = 10;
+const RETRY_DELAY = 2000;
+
+const { actions, state } = store( 'wporg/patterns/thumbnail', {
+ state: {
+ attempts: 0,
+ shouldRetry: true,
+ hasError: false,
+ get base64Image() {
+ return getContext().base64Image;
+ },
+ get hasLoaded() {
+ return state.base64Image || state.hasError;
+ },
+ },
+ actions: {
+ setShouldRetry( value ) {
+ state.shouldRetry = value;
+ },
+
+ setHasError( value ) {
+ state.hasError = value;
+ },
+
+ setBase64Image( value ) {
+ const context = getContext();
+ context.base64Image = value;
+ },
+
+ *fetchImage( fullUrl ) {
+ try {
+ const res = yield fetch( fullUrl );
+ state.attempts++;
+
+ if ( res.redirected ) {
+ actions.setShouldRetry( true );
+ } else if ( res.status === 200 && ! res.redirected ) {
+ const blob = yield res.blob();
+
+ const value = yield new Promise( ( resolve ) => {
+ const reader = new FileReader();
+ reader.onloadend = () => resolve( reader.result );
+ reader.readAsDataURL( blob );
+ } );
+
+ actions.setBase64Image( value );
+ actions.setShouldRetry( false );
+ }
+ } catch ( error ) {
+ actions.setHasError( true );
+ actions.setShouldRetry( false );
+ }
+ },
+ },
+
+ callbacks: {
+ // Run on init, starts the image fetch process.
+ *init() {
+ const { src } = getContext();
+
+ if ( ! state.base64Image ) {
+ // Initial fetch.
+ yield actions.fetchImage( src );
+
+ while ( state.shouldRetry ) {
+ yield new Promise( ( resolve ) => {
+ setTimeout( () => resolve(), RETRY_DELAY );
+ } );
+ yield actions.fetchImage( src );
+
+ if ( state.attempts >= MAX_ATTEMPTS ) {
+ actions.setHasError( true );
+ actions.setShouldRetry( false );
+ }
+ }
+ }
+ },
+ },
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/block.json
new file mode 100644
index 000000000..aa5015902
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/block.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/post-status",
+ "version": "0.1.0",
+ "title": "Post Status",
+ "category": "design",
+ "icon": "",
+ "description": "A label displaying the current pattern's status.",
+ "textdomain": "wporg",
+ "attributes": {},
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId", "postType" ],
+ "style": "file:./style-index.css",
+ "editorScript": "file:./index.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/index.js
new file mode 100644
index 000000000..f56b8b425
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from '../../utils/dynamic-edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/index.php
new file mode 100644
index 000000000..6b16b4325
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/index.php
@@ -0,0 +1,66 @@
+ __NAMESPACE__ . '\render',
+ )
+ );
+}
+
+/**
+ * Render the block content.
+ *
+ * @param array $attributes Block attributes.
+ * @param string $content Block default content.
+ * @param WP_Block $block Block instance.
+ *
+ * @return string Returns the block markup.
+ */
+function render( $attributes, $content, $block ) {
+ $post_id = $block->context['postId'];
+ if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
+ return '';
+ }
+
+ $status = get_post_status( $post_id );
+
+ $label = '';
+ switch ( $status ) {
+ case 'publish':
+ $label = __( 'Published', 'wporg-patterns' );
+ $type = 'published';
+ break;
+ case 'pending-review': // Potential spam.
+ case 'pending':
+ $label = __( 'Pending', 'wporg-patterns' );
+ $type = 'pending';
+ break;
+ case 'draft':
+ $label = __( 'Draft', 'wporg-patterns' );
+ $type = 'draft';
+ break;
+ case 'unlisted':
+ $label = __( 'Declined', 'wporg-patterns' );
+ $type = 'unlisted';
+ break;
+ }
+
+ if ( ! $label ) {
+ return '';
+ }
+
+ $wrapper_attributes = get_block_wrapper_attributes( [ 'class' => 'is-' . $type ] );
+ return sprintf( '%2s
', $wrapper_attributes, $label );
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/style.scss
new file mode 100644
index 000000000..d427e4200
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/post-status/style.scss
@@ -0,0 +1,36 @@
+.wp-block-wporg-post-status {
+ display: inline-block;
+ padding: 0.1rem 0.75rem;
+ border-radius: 2px;
+ background: var(--wp--preset--color--blueberry-4);
+ color: var(--wp--preset--color--charcoal-1);
+ font-weight: 600;
+ font-size: var(--wp--preset--font-size--extra-small);
+ line-height: var(--wp--custom--body--extra-small--typography--line-height);
+ text-transform: uppercase;
+
+ &.is-pending,
+ &.is-pending-review {
+ background: var(--wp--preset--color--lemon-2);
+ }
+
+ &.is-unlisted {
+ background: var(--wp--preset--color--pomegrade-3);
+ }
+
+ &.is-published {
+ background: var(--wp--preset--color--acid-green-2);
+ }
+}
+
+// Adjust position of the post status chip when in my patterns grid.
+.wporg-my-patterns .wp-block-post {
+ position: relative;
+
+ .wp-block-wporg-post-status {
+ position: absolute;
+ top: calc(var(--wp--preset--spacing--10) * -1);
+ left: var(--wp--preset--spacing--10);
+ margin-block-start: 0 !important;
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/block.json
new file mode 100644
index 000000000..ff12b8654
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/block.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/report-pattern",
+ "version": "0.1.0",
+ "title": "Report Pattern",
+ "category": "design",
+ "icon": "",
+ "description": "A button to trigger the report flow, including report modal.",
+ "textdomain": "wporg",
+ "attributes": {},
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId", "postType" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "viewScript": "file:./view.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/index.js
new file mode 100644
index 000000000..f56b8b425
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from '../../utils/dynamic-edit';
+import metadata from './block.json';
+import './style.scss';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/index.php
new file mode 100644
index 000000000..a01874b37
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/index.php
@@ -0,0 +1,15 @@
+block_type->view_script ) ) {
+ wp_enqueue_script( $block->block_type->view_script );
+ // Move to footer.
+ wp_script_add_data( $block->block_type->view_script, 'group', 1 );
+}
+
+$post_id = $block->context['postId'];
+if ( ! $post_id ) {
+ return;
+}
+
+if ( ! current_user_can( 'read' ) ) {
+ return;
+}
+
+// If this pattern has been reported by this user, it can't be reported again.
+if ( user_has_flagged_pattern() ) {
+ printf(
+ '%s
',
+ get_block_wrapper_attributes(),
+ __( 'You’ve reported this pattern.', 'wporg-patterns' )
+ );
+ return;
+}
+
+$reasons = get_terms( [ 'taxonomy' => FLAG_REASON, 'hide_empty' => false, 'orderby' => 'slug' ] );
+
+?>
+>
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/style.scss b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/style.scss
new file mode 100644
index 000000000..e202523fb
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/style.scss
@@ -0,0 +1,109 @@
+.wp-block-wporg-report-pattern {
+ // stylelint-disable-next-line max-line-length
+ --wp-report-dialog-offset: calc(var(--wp-admin--admin-bar--height, 0px) + var(--wp--custom--local-navigation-bar--spacing--height, 0px));
+}
+
+.wporg-report-pattern__dialog-container,
+.wporg-report-pattern__dialog-overlay {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.wporg-report-pattern__dialog-container {
+ top: var(--wp-report-dialog-offset);
+ z-index: 2;
+ display: flex;
+
+ &[aria-hidden="true"] {
+ display: none;
+ }
+}
+
+.wporg-report-pattern__dialog-overlay {
+ background-color: var(--wp--preset--color--black-opacity-15);
+}
+
+.wporg-report-pattern__dialog-content {
+ z-index: 2;
+ position: relative;
+ margin: auto;
+ background-color: var(--wp--preset--color--white);
+
+ h1 {
+ margin: 0;
+ font-family: var(--wp--preset--font-family--inter) !important;
+ font-size: var(--wp--preset--font-size--large) !important;
+ font-weight: 600;
+ align-self: center;
+ }
+}
+
+.wporg-report-pattern__dialog-header {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: var(--wp--preset--spacing--20);
+ padding: var(--wp--preset--spacing--20);
+ border-bottom: 1px solid var(--wp--preset--color--black-opacity-15);
+
+ button {
+ padding: 0;
+ height: 24px;
+ width: 24px;
+ border: none;
+ background: transparent;
+ }
+}
+
+.wporg-report-pattern__dialog-body {
+ padding: var(--wp--preset--spacing--20);
+ display: flex;
+ flex-direction: column;
+ gap: var(--wp--preset--spacing--20);
+ overflow: auto;
+ max-height: calc(85vh - var(--wp-report-dialog-offset) - 100px);
+ font-size: var(--wp--preset--font-size--small);
+
+ fieldset,
+ legend {
+ border: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ .wporg-report-pattern__dialog-field > legend,
+ .wporg-report-pattern__dialog-field > label {
+ font-weight: 600;
+ }
+
+ textarea {
+ border: 1px solid var(--wp--preset--color--black-opacity-15);
+ }
+
+ textarea:focus,
+ input:focus {
+ outline: 1.5px solid var(--wp--preset--color--blueberry-1);
+ outline-offset: -1.5px;
+ box-shadow: inset 0 0 0 3px var(--wp--preset--color--white);
+ }
+}
+
+.wporg-report-pattern__dialog-footer {
+ display: flex;
+ flex-direction: row;
+ gap: var(--wp--preset--spacing--20);
+ padding: var(--wp--preset--spacing--20);
+ border-top: 1px solid var(--wp--preset--color--black-opacity-15);
+
+ > * {
+ flex: 1;
+
+ button {
+ width: 100%;
+ }
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/view.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/view.js
new file mode 100644
index 000000000..82d5e15d0
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/report-pattern/view.js
@@ -0,0 +1,22 @@
+/**
+ * Internal dependencies
+ */
+import A11yDialog from 'a11y-dialog';
+
+const init = () => {
+ const element = document.getElementById( 'report-dialog' );
+ if ( ! element ) {
+ return;
+ }
+ // Initialize dialog.
+ const dialog = new A11yDialog( element );
+
+ // Open dialog with button.
+ const button = document.querySelector( '.wp-block-wporg-report-pattern [data-a11y-dialog-show]' );
+ button.disabled = false;
+ button.addEventListener( 'click', () => {
+ dialog.show();
+ } );
+};
+
+window.addEventListener( 'load', init );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/block.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/block.json
new file mode 100644
index 000000000..0cb8a7965
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/block.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/status-notice",
+ "version": "0.1.0",
+ "title": "Status Notice",
+ "category": "design",
+ "icon": "",
+ "description": "A notice displaying the current pattern's status.",
+ "textdomain": "wporg",
+ "attributes": {},
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId", "postType" ],
+ "editorScript": "file:./index.js",
+ "render": "file:./render.php"
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/index.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/index.js
new file mode 100644
index 000000000..7633e1acc
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/index.js
@@ -0,0 +1,15 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import Edit from '../../utils/dynamic-edit';
+import metadata from './block.json';
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/index.php b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/index.php
new file mode 100644
index 000000000..b9f611648
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/blocks/status-notice/index.php
@@ -0,0 +1,94 @@
+ __NAMESPACE__ . '\render',
+ )
+ );
+}
+
+/**
+ * Render the block content.
+ *
+ * @param array $attributes Block attributes.
+ * @param string $content Block default content.
+ * @param WP_Block $block Block instance.
+ *
+ * @return string Returns the block markup.
+ */
+function render( $attributes, $content, $block ) {
+ $post_id = $block->context['postId'];
+ if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
+ return '';
+ }
+
+ $type = 'info';
+ $status = get_post_status( $post_id );
+
+ $message = '';
+ switch ( $status ) {
+ case 'pending-review': // Potential spam.
+ case 'pending':
+ $type = 'alert';
+ $message .= '
';
+ $message .= '' . __( 'Review pending.', 'wporg-patterns' ) . ' ';
+ $message .= __( 'This pattern is only visible to you. Once approved it will be published to everyone.', 'wporg-patterns' );
+ $message .= '
';
+ $message .= '
' . __( 'All patterns submitted to WordPress.org are subject to both automated and manual approval. It might take a few days for your pattern to be approved.', 'wporg-patterns' ) . '
';
+ $message .= '
' . __( 'Reviewers look for content that may be problematic (copyright or trademark issues) and whether your pattern works as intended.', 'wporg-patterns' ) . '
';
+ break;
+ case 'draft':
+ $message .= '
';
+ $message .= '' . __( 'Saved as draft.', 'wporg-patterns' ) . ' ';
+ $message .= __( 'This pattern is only visible to you. When you’re ready, submit it to be published to everyone.', 'wporg-patterns' );
+ $message .= '
';
+ $message .= '
' . __( 'Patterns can be saved as a draft which can be submitted for approval at any time. This allows you to save your design and come back to it later to submit.', 'wporg-patterns' ) . '
';
+ break;
+ case 'unlisted':
+ $type = 'warning';
+ $reason = get_pattern_unlisted_reason( $post_id );
+ $message .= '
';
+ $message .= '' . __( 'Pattern declined.', 'wporg-patterns' ) . ' ';
+ $message .= __( 'WordPress.org has chosen not to include this pattern in the directory.', 'wporg-patterns' );
+ $message .= '
';
+ if ( $reason ) {
+ $message .= sprintf(
+ '
%s %s
',
+ __( 'WordPress.org has removed your pattern from the directory for the following reason:', 'wporg-patterns' ),
+ $reason
+ );
+ }
+ $message .= '
' . __( 'You can update your pattern to resubmit it for approval at any time.', 'wporg-patterns' ) . '
';
+ break;
+ case 'publish':
+ $type = 'tip';
+ $message .= '
';
+ $message .= '' . __( 'Pattern published!', 'wporg-patterns' ) . ' ';
+ $message .= __( 'Your new design is now available to everyone.', 'wporg-patterns' );
+ $message .= '
';
+ break;
+ }
+
+ $markup = '
+
+ ';
+
+ return do_blocks( sprintf( $markup, $type, $message ) );
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/utils/copy-to-clipboard.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/utils/copy-to-clipboard.js
new file mode 100644
index 000000000..3af919703
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/utils/copy-to-clipboard.js
@@ -0,0 +1,29 @@
+/**
+ * Uses a hidden textarea that is added and removed from the DOM in order to copy to clipboard via the Browser.
+ *
+ * @param {string} stringToCopy A string that will be copied to the clipboard
+ * @return {boolean} Whether the copy function succeeded
+ */
+export default function ( stringToCopy ) {
+ const element = document.createElement( 'textarea' );
+
+ // We don't want the text area to be selected since it's temporary.
+ element.setAttribute( 'readonly', '' );
+
+ // We don't want screen readers to read the content since it's pattern markup
+ element.setAttribute( 'aria-hidden', 'true' );
+
+ // We don't want the text area to be visible since it's temporary.
+ element.style.position = 'absolute';
+ element.style.left = '-9999px';
+
+ element.value = stringToCopy;
+
+ document.body.appendChild( element );
+ element.select();
+
+ const success = document.execCommand( 'copy' );
+ document.body.removeChild( element );
+
+ return success;
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/src/utils/dynamic-edit.js b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/utils/dynamic-edit.js
new file mode 100644
index 000000000..62679697b
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/src/utils/dynamic-edit.js
@@ -0,0 +1,20 @@
+/**
+ * WordPress dependencies
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+import ServerSideRender from '@wordpress/server-side-render';
+
+export default function Edit( { name, attributes, context } ) {
+ const blockProps = useBlockProps();
+ const { postId } = context;
+ return (
+
+
+
+ );
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/style.css b/public_html/wp-content/themes/wporg-pattern-directory-2024/style.css
new file mode 100644
index 000000000..be5a94ec9
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/style.css
@@ -0,0 +1,136 @@
+/*
+ * Theme Name: WordPress.org Pattern Directory 2024
+ * Theme URI: https://github.com/WordPress/pattern-directory
+ * Author: WordPress.org
+ * Author URI: https://wordpress.org/
+ * Description:
+ * Version: 1.0.0
+ * License: GNU General Public License v2 or later
+ * Text Domain: wporg
+ * Template: wporg-parent-2021
+ */
+
+/*
+ * Note: only add styles here in cases where you can't achieve the style with
+ * templates or theme.json settings.
+ */
+
+/* Fix image alignment */
+.wp-block-avatar__image {
+ vertical-align: middle;
+}
+
+.wp-block-query-pagination.wp-block-query-pagination {
+ margin-bottom: 0;
+}
+
+/*
+ * If two spacers are immediate siblings, something isn't rendering
+ * (like pagination, etc). So hide the subsequent spacers.
+ */
+.wp-block-query .wp-block-spacer + .wp-block-spacer {
+ display: none;
+}
+
+.wp-block-query-no-results {
+ margin-top: 0;
+}
+
+/* Both blocks are in the local header, but */
+body.blog .wp-block-wporg-local-navigation-bar .wp-block-post-title,
+body.archive .wp-block-wporg-local-navigation-bar .wp-block-post-title,
+body.search .wp-block-wporg-local-navigation-bar .wp-block-post-title {
+ display: none;
+}
+
+body.single .wp-block-wporg-local-navigation-bar .wp-block-query-title,
+body.page .wp-block-wporg-local-navigation-bar .wp-block-query-title {
+ display: none;
+}
+
+/*
+ * A linked post title should be blueberry (needed to override parent setting,
+ * where post title is the same for linked or not).
+ */
+.wp-block-post-title a:where(:not(.wp-element-button)) {
+ color: var(--wp--preset--color--blueberry-1);
+}
+
+/* Add default focus style. */
+:where(main) a:where(:not(.wp-element-button)):focus,
+:where(main) button:where(:not([class*="wp-block-button"])):focus {
+ outline: none;
+ border-radius: 2px;
+ box-shadow: 0 0 0 1.5px var(--wp--custom--link--color--text);
+}
+
+/* Style pattern tags. */
+.wp-block-post-terms {
+ line-height: var(--wp--custom--body--small--typography--line-height);
+}
+
+.wp-block-post-terms a {
+ text-decoration: none;
+ display: inline-block;
+ padding: 0.25rem 1rem;
+ border-radius: 1.5em;
+ background: var(--wp--preset--color--blueberry-4);
+ color: var(--wp--preset--color--blueberry-1);
+}
+
+.wp-block-post-terms a:hover,
+.wp-block-post-terms a:focus {
+ text-decoration: underline;
+}
+
+/* Hide the separator so it still takes up space. */
+.wp-block-post-terms .wp-block-post-terms__separator {
+ display: inline-block;
+ visibility: hidden;
+ width: var(--wp--preset--spacing--10);
+}
+
+.wp-block-button.is-edit-link a {
+ display: flex;
+ align-items: center;
+ gap: 0.25em;
+
+ /* Adjust the size of the button to make it more inline with the title. */
+ --wp--custom--button--spacing--padding--top: 3px !important;
+ --wp--custom--button--spacing--padding--bottom: 3px !important;
+ --wp--custom--button--spacing--padding--left: 6px !important;
+ --wp--custom--button--spacing--padding--right: 6px !important;
+}
+
+.wp-block-button.is-edit-link a:before {
+ content: '';
+ display: inline-block;
+ height: 1.5em;
+ width: 1.5em;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='25' fill='none' viewBox='0 0 24 25'%3E%3Cpath fill='%233858E9' d='m19 7.478-3-3-8.5 8.5-1 4 4-1 8.5-8.5ZM12 18.978H5v1.5h7v-1.5Z'/%3E%3C/svg%3E");
+ background-size: contain;
+}
+
+.wp-block-button.is-edit-link a:active:before {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='25' fill='none' viewBox='0 0 24 25'%3E%3Cpath fill='%23ffffff' d='m19 7.478-3-3-8.5 8.5-1 4 4-1 8.5-8.5ZM12 18.978H5v1.5h7v-1.5Z'/%3E%3C/svg%3E");
+}
+
+/* Drop to a two column layout for grids (except the "more by"). */
+@media (max-width: 1280px) {
+ :where(body:not(.single-wporg-pattern)) .wp-block-post-template.is-layout-grid.columns-3 {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+/* Pages using this have the 1760 wide width. */
+.wporg-patterns-nested-alignfull {
+ width: 100vw;
+ margin-inline-start: calc(1760px / 2 - 50vw);
+}
+
+/* 1920 = 1760 + 2 * edge-space. */
+@media (max-width: 1920px) {
+ .wporg-patterns-nested-alignfull {
+ margin-inline-start: calc(var(--wp--preset--spacing--edge-space) * -1);
+ }
+}
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/archive.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/archive.html
new file mode 100644
index 000000000..7a68ecd00
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/archive.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/front-page.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/front-page.html
new file mode 100644
index 000000000..332c4c60d
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/front-page.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/index.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/index.html
new file mode 100644
index 000000000..7a68ecd00
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page-favorites.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page-favorites.html
new file mode 100644
index 000000000..dc1ec8b3b
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page-favorites.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page-my-patterns.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page-my-patterns.html
new file mode 100644
index 000000000..5d9d4889e
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page-my-patterns.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page.html
new file mode 100644
index 000000000..3b0e6af4d
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/page.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/search.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/search.html
new file mode 100644
index 000000000..1df974299
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/search.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/single.html b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/single.html
new file mode 100644
index 000000000..a567e7d7e
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/templates/single.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public_html/wp-content/themes/wporg-pattern-directory-2024/theme.json b/public_html/wp-content/themes/wporg-pattern-directory-2024/theme.json
new file mode 100644
index 000000000..433155bfa
--- /dev/null
+++ b/public_html/wp-content/themes/wporg-pattern-directory-2024/theme.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/theme.json",
+ "version": 2,
+ "settings": {
+ "custom": {
+ "wporg-pattern-thumbnail": {
+ "border": {
+ "color": "rgba(0, 0, 0, 0.10)",
+ "width": "1px",
+ "radius": "2px"
+ }
+ },
+ "wporg-pattern-preview": {
+ "border": {
+ "color": "var(--wp--preset--color--light-grey-2)",
+ "width": "20px"
+ }
+ }
+ }
+ },
+ "styles": {
+ "blocks": {
+ "core/post-title": {
+ "elements": {
+ "link": {
+ "color": {
+ "text": "var(--wp--preset--color--blueberry-1)"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index f5338d23b..dc7c42f1c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4810,6 +4810,13 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+a11y-dialog@^8.0.4:
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/a11y-dialog/-/a11y-dialog-8.0.4.tgz#731a983d38d059665ff7e62daccedf5214131be8"
+ integrity sha512-MQrLxqLPx4E8+ynGwulBs6OjtxQT/XemgnlNPb+cR8K/qdwHBQv1WZQZpBuTVhlWGpAwWmSYffUZ/78anhcD3w==
+ dependencies:
+ focusable-selectors "^0.8.0"
+
abab@^2.0.5, abab@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
@@ -7607,6 +7614,11 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+focusable-selectors@^0.8.0:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/focusable-selectors/-/focusable-selectors-0.8.4.tgz#2ee34129b29371766ce201499e3b88c6f79ea4c1"
+ integrity sha512-0XxbkD0KhOnX10qmnfF9U8DkDD8N/e4M77wMYw2Itoi4vdcoRjSkqXLZFIzkrLIOxzmzCGy88fNG1EbeXMD/zw==
+
follow-redirects@^1.0.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"