Compare commits
10 Commits
923c1d6f9a
...
fc871dcd75
| Author | SHA1 | Date | |
|---|---|---|---|
| fc871dcd75 | |||
| ac14f7083b | |||
| 3663766e2c | |||
| 40f924c5dd | |||
| ebcc0de0aa | |||
| 174fab8b16 | |||
| f505687b16 | |||
| 9ea053986d | |||
| 2f8396d994 | |||
| 3accc85817 |
79
includes/class-partnerexpo-core-user-fields.php
Normal file
79
includes/class-partnerexpo-core-user-fields.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Used to register custom user fields.
|
||||
*
|
||||
* @link https://github.com/Duskell
|
||||
* @since 1.1.0
|
||||
* @package Partnerexpo_Core
|
||||
* @subpackage Partnerexpo_Core/includes
|
||||
* @author Juhász Levente <juhasz.levente@rendszerepito.hu>
|
||||
*/
|
||||
class Partnerexpo_Core_User_Fields extends Partnerexpo_Core {
|
||||
protected $loader;
|
||||
|
||||
public function __construct( $loader ) {
|
||||
$this->loader = $loader;
|
||||
$this->activate_fields();
|
||||
}
|
||||
|
||||
private function activate_fields() {
|
||||
$this->loader->add_action( 'show_user_profile', $this, 'add_custom_user_fields' );
|
||||
$this->loader->add_action( 'edit_user_profile', $this, 'add_custom_user_fields' );
|
||||
$this->loader->add_action( 'personal_options_update', $this, 'save_custom_user_fields' );
|
||||
$this->loader->add_action( 'edit_user_profile_update', $this, 'save_custom_user_fields' );
|
||||
$this->loader->add_action( 'user_register', $this, 'add_value_after_reg' );
|
||||
$this->loader->add_action( 'register_form', $this, 'registration_form' );
|
||||
$this->loader->add_filter( 'registration_errors', $this, 'registration_errors', 10, 3 );
|
||||
}
|
||||
|
||||
public function add_custom_user_fields( $user ) {
|
||||
$company = get_user_meta( $user->ID, 'company', true );
|
||||
?>
|
||||
<h3>Additional Information</h3>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="company"><?php _e( 'Cég neve', 'partnerexpo-core' ) ?></label></th>
|
||||
<td>
|
||||
<input type="text" name="company" id="company" value="<?php echo esc_attr( $company ) ?>" class="regular-text" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function save_custom_user_fields( $user_id ) {
|
||||
if( ! isset( $_POST[ '_wpnonce' ] ) || ! wp_verify_nonce( $_POST[ '_wpnonce' ], 'update-user_' . $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( ! current_user_can( 'edit_user', $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_user_meta( $user_id, 'company', sanitize_text_field( $_POST[ 'company' ] ) );
|
||||
}
|
||||
|
||||
public function add_value_after_reg( $user_id ) {
|
||||
if ( ! empty( $_POST['company'] ) ) {
|
||||
update_user_meta( $user_id, 'company', sanitize_text_field( $_POST[ 'company' ] ?? '' ) );
|
||||
}
|
||||
}
|
||||
|
||||
function registration_form() {
|
||||
?>
|
||||
<p>
|
||||
<label for="company"><?php _e( 'Cég neve', 'partnerexpo-core' ) ?><br />
|
||||
<input type="text" name="company" id="company" class="input" value="" size="25" /></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
function registration_errors( $errors, $sanitized_user_login, $user_email ) {
|
||||
if ( empty( $_POST['company'] ) || ! empty( $_POST['company'] ) && trim( $_POST['company'] ) == '' ) {
|
||||
$errors->add( 'company_error', sprintf('<strong>%s</strong>: %s',__( 'Error', 'partnerexpo-core' ),__( 'Cég megadása kötelező!', 'partnerexpo-core' ) ) );
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
@@ -121,8 +121,18 @@ class Partnerexpo_Core {
|
||||
*/
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'public/class-partnerexpo-core-public.php';
|
||||
|
||||
$this->loader = new Partnerexpo_Core_Loader();
|
||||
/**
|
||||
* Used to have relevance-based search results.
|
||||
*/
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/dependency/class-query-w-relevance.php';
|
||||
|
||||
/**
|
||||
* The class responsible for registering custom user fields.
|
||||
*/
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-partnerexpo-core-user-fields.php';
|
||||
|
||||
$this->loader = new Partnerexpo_Core_Loader();
|
||||
$user_fields = new Partnerexpo_Core_User_Fields( $this->loader );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,8 +177,8 @@ class Partnerexpo_Core {
|
||||
private function define_public_hooks() {
|
||||
$plugin_public = new Partnerexpo_Core_Public( $this->get_plugin_name(), $this->get_version() );
|
||||
|
||||
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' );
|
||||
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' );
|
||||
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'register_styles' );
|
||||
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'register_scripts' );
|
||||
$this->loader->add_action( 'rest_api_init', $plugin_public, 'register_endpoint' );
|
||||
$this->loader->add_action( 'init', $this, 'define_cpts' );
|
||||
$this->loader->add_action( 'init', $this, 'define_taxonomies' );
|
||||
@@ -207,7 +217,7 @@ class Partnerexpo_Core {
|
||||
'public' => true,
|
||||
'has_archive' => false,
|
||||
'show_in_rest' => true,
|
||||
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt' ],
|
||||
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'author' ],
|
||||
'rewrite' => [
|
||||
'slug' => __( 'partnerek', 'partnerexpo-core' ),
|
||||
'pages' => false,
|
||||
|
||||
130
includes/dependency/class-query-w-relevance.php
Normal file
130
includes/dependency/class-query-w-relevance.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
//
|
||||
// a WP_Query subclass which adds a Relevance score and sorts by it
|
||||
// https://github.com/GreenInfo-Network/WP_Query_WithRelevance
|
||||
//
|
||||
if (! defined( 'WPINC')) die;
|
||||
|
||||
class WP_Query_WithRelevance extends WP_Query {
|
||||
//
|
||||
// search field DEFAULT weights
|
||||
// the $args passed to this Query may/should specify weightings for specific taxonomies, meta keys, etc.
|
||||
// but these act as defaults
|
||||
//
|
||||
var $DEFAULT_WEIGHTING_TITLE_KEYWORD = 1.0;
|
||||
var $DEFAULT_WEIGHTING_CONTENT_KEYWORD = 0.25;
|
||||
var $DEFAULT_WEIGHTING_TAXONOMY_RATIO = 10.0;
|
||||
|
||||
//
|
||||
// constructor
|
||||
// performs a standard WP_Query but then postprocesses to add relevance, then sort by that relevance
|
||||
//
|
||||
public function __construct($args = array()) {
|
||||
// stow and unset the orderby param
|
||||
// cuz it's not a real DB field that can be used
|
||||
if ($args['orderby'] === 'relevance') {
|
||||
$this->orderby = $args['orderby'];
|
||||
$this->order = 'DESC';
|
||||
unset($args['orderby']);
|
||||
unset($args['order']);
|
||||
}
|
||||
|
||||
// perform a typical WP_Query
|
||||
// then if we weren't using a relevance sorting, we're actually done
|
||||
$this->process_args($args);
|
||||
parent::__construct($args);
|
||||
if (! $this->orderby) return;
|
||||
|
||||
// okay, we're doing relevance postprocessing
|
||||
$this->initialize_relevance_scores();
|
||||
$this->score_keyword_relevance();
|
||||
$this->score_taxonomy_relevance();
|
||||
$this->orderby_relevance();
|
||||
|
||||
// debugging; you can display this at any time to just dump the list of results
|
||||
//$this->display_results_so_far();
|
||||
}
|
||||
|
||||
// initializing all posts' relevance scores to 0
|
||||
private function initialize_relevance_scores() {
|
||||
foreach ($this->posts as $post) {
|
||||
$post->relevance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function score_keyword_relevance() {
|
||||
if (! $this->query_vars['s']) return; // no keyword string = this is a noop
|
||||
|
||||
$weight_title = @$this->query_vars['relevance_scoring']['title_keyword'];
|
||||
$weight_content = @$this->query_vars['relevance_scoring']['content_keyword'];
|
||||
if ($weight_title == NULL) $weight_title = $this->DEFAULT_WEIGHTING_TITLE_KEYWORD;
|
||||
if ($weight_content == NULL) $weight_content = $this->DEFAULT_WEIGHTING_CONTENT_KEYWORD;
|
||||
// print "score_keyword_relevance() Title keyword weight {$weight_title}\n";
|
||||
// print "score_keyword_relevance() Content keyword weight {$weight_content}\n";
|
||||
|
||||
$words = strtoupper(trim($this->query_vars['s']));
|
||||
$words = preg_split('/\s+/', $words);
|
||||
|
||||
foreach ($this->posts as $post) {
|
||||
$title = strtoupper($post->post_title);
|
||||
$content = strtoupper($post->post_content);
|
||||
|
||||
foreach ($words as $thisword) {
|
||||
$post->relevance += substr_count($title, $thisword) * $weight_title;
|
||||
$post->relevance += substr_count($content, $thisword) * $weight_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function score_taxonomy_relevance() {
|
||||
if (!isset($this->query_vars['tax_query']) || !is_array($this->query_vars['tax_query'])) return; // no taxo query = skip it
|
||||
|
||||
// taxonomy relevance is only calculated for IN-list operations
|
||||
// for other types of queries, all posts match that value and further scoring would be pointless
|
||||
|
||||
// go over each taxo and each post
|
||||
// increase the post relevance, based on number of terms it has in common with the terms we asked about
|
||||
// this is done one taxo at a time, so we can match terms by ID, by slug, or by name ... and so we can apply individual weighting by that taxo
|
||||
foreach ($this->query_vars['tax_query'] as $taxo) {
|
||||
if (strtoupper($taxo['operator']) !== 'IN' or ! is_array($taxo['terms'])) continue; // not a IN-list query, so relevance scoring is not useful for this taxo
|
||||
|
||||
$taxoslug = $taxo['taxonomy'];
|
||||
$whichfield = $taxo['field'];
|
||||
$wantterms = $taxo['terms'];
|
||||
|
||||
$taxo_weighting = @$this->query_vars['relevance_scoring']['tax_query'][$taxoslug];
|
||||
if ($taxo_weighting === NULL) $taxo_weighting = $this->DEFAULT_WEIGHTING_TAXONOMY_RATIO;
|
||||
// print "score_taxonomy_relevance() Taxo {$taxoslug} weight {$taxo_weighting}\n";
|
||||
|
||||
foreach ($this->posts as $post) {
|
||||
// find number of terms in common between this post and this taxo's list
|
||||
$terms_in_common = 0;
|
||||
$thispostterms = get_the_terms($post->ID, $taxo['taxonomy']);
|
||||
|
||||
foreach ($thispostterms as $hasthisterm) {
|
||||
if (in_array($hasthisterm->{$whichfield}, $wantterms)) $terms_in_common += 1;
|
||||
}
|
||||
|
||||
// express that terms-in-common as a percentage, and add to this post's relevance score
|
||||
$ratio = (float) $terms_in_common / sizeof($wantterms);
|
||||
$post->relevance += ($ratio * $ratio * $taxo_weighting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function orderby_relevance() {
|
||||
usort($this->posts, array($this, 'usort_sorting'));
|
||||
}
|
||||
|
||||
private function display_results_so_far () { // for debugging
|
||||
foreach ($this->posts as $post) {
|
||||
printf('%d %s = %.1f' . "\n", $post->ID, $post->post_title, $post->relevance) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private function usort_sorting ($p, $q) {
|
||||
// we force DESC and only trigger if orderby==='relevance' so we can keep this simple
|
||||
if ($p->relevance == $q->relevance) return 0;
|
||||
return $p->relevance > $q->relevance ? -1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
* Plugin Name: PartnerExpo Core
|
||||
* Plugin URI: https://partnerexpo.eu
|
||||
* Description: A PartnerEXPO oldal belső pluginja
|
||||
* Version: 1.0.9
|
||||
* Version: 1.1.2
|
||||
* Author: Juhász Levente
|
||||
* Author URI: https://github.com/Duskell/
|
||||
* License: GPL-2.0+
|
||||
@@ -23,7 +23,7 @@ if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
define( 'PARTNEREXPO_CORE_VERSION', '1.0.9' );
|
||||
define( 'PARTNEREXPO_CORE_VERSION', '1.1.2' );
|
||||
|
||||
function activate_partnerexpo_core() {
|
||||
require_once plugin_dir_path( __FILE__ ) . 'includes/class-partnerexpo-core-activator.php';
|
||||
|
||||
@@ -70,6 +70,15 @@ class Partnerexpo_Core_Public {
|
||||
$params['tags'] = str_replace(',', '+', $params['tags']);
|
||||
}
|
||||
|
||||
$companies = explode(',', $params['companies'] ?? '');
|
||||
|
||||
$authors = get_users([
|
||||
'meta_key' => 'company',
|
||||
'meta_value' => $companies,
|
||||
'meta_compare' => 'IN',
|
||||
'fields' => 'ID',
|
||||
]);
|
||||
|
||||
$args = [
|
||||
'post_type' => 'pexpo_partners',
|
||||
'posts_per_page' => $params['resultsPerPage'] ?? 10,
|
||||
@@ -79,6 +88,32 @@ class Partnerexpo_Core_Public {
|
||||
'pexpo_tags' => $params['tags'] ?? '',
|
||||
];
|
||||
|
||||
if (!empty($authors)) {
|
||||
$args['author__in'] = array_values($authors);
|
||||
}
|
||||
|
||||
if ( ! empty( $params['tags'] ) ) {
|
||||
$tag_string = $params['tags'];
|
||||
$operator = 'IN';
|
||||
$delimiter = ',';
|
||||
|
||||
if ( strpos( $tag_string, '+' ) !== false ) {
|
||||
$operator = 'AND';
|
||||
$delimiter = '+';
|
||||
}
|
||||
|
||||
$tag_slugs = explode( $delimiter, $tag_string );
|
||||
|
||||
$args['tax_query'] = [
|
||||
[
|
||||
'taxonomy' => 'pexpo_tags',
|
||||
'field' => 'slug',
|
||||
'terms' => $tag_slugs,
|
||||
'operator' => $operator,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
switch ($params['sort'] ?? 'relevance') {
|
||||
case 'date_asc':
|
||||
$args['orderby'] = 'date';
|
||||
@@ -97,19 +132,26 @@ class Partnerexpo_Core_Public {
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'relevance':
|
||||
$args['orderby'] = 'relevance';
|
||||
break;
|
||||
default:
|
||||
// Default WordPress search sorting
|
||||
break;
|
||||
}
|
||||
|
||||
$query = new WP_Query($args);
|
||||
$query = new WP_Query_WithRelevance($args);
|
||||
|
||||
$response = [
|
||||
'pages' => $query->max_num_pages,
|
||||
'found' => $query->found_posts,
|
||||
'results' => [],
|
||||
];
|
||||
|
||||
$posts = [];
|
||||
|
||||
foreach ($query->posts as $post) {
|
||||
$tags = wp_get_post_terms($post->ID, 'pexpo_tags', ['fields' => 'names']);
|
||||
|
||||
$posts[] = [
|
||||
$response['results'][] = [
|
||||
'id' => $post->ID,
|
||||
'title' => $post->post_title,
|
||||
'image' => get_the_post_thumbnail_url($post->ID),
|
||||
@@ -117,13 +159,12 @@ class Partnerexpo_Core_Public {
|
||||
'date' => date('Y-m-d', strtotime($post->post_date)),
|
||||
'tag' => $tags ?? null,
|
||||
'url' => get_permalink($post),
|
||||
'order' => $params['sort'] ?? 'relevance',
|
||||
];
|
||||
}
|
||||
|
||||
wp_reset_postdata();
|
||||
|
||||
return rest_ensure_response($posts);
|
||||
return rest_ensure_response($response);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
--accent1: #950000;
|
||||
--accent2: #2c3489;
|
||||
|
||||
--darker: 30%;
|
||||
--lighter: 40%;
|
||||
|
||||
--bg: #f6f7fb;
|
||||
--panel: #ffffff;
|
||||
--panel2: #f2f4fb;
|
||||
@@ -20,9 +23,9 @@
|
||||
|
||||
--filterW: 170px;
|
||||
--filterWOpen: 360px;
|
||||
|
||||
--toggle-bg-color: #4281A4;
|
||||
--toggle-nub-color: #FF686B;
|
||||
--toggle-bg-off: #ca0000;
|
||||
--toggle-bg-on: #10bb2d;
|
||||
--toggle-nub-color: #f6f7fb;
|
||||
|
||||
--topbarH: 64px;
|
||||
|
||||
@@ -272,6 +275,8 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
row-gap: 6px;
|
||||
padding-right: 3px;
|
||||
max-width: 170px;
|
||||
}
|
||||
|
||||
.pexpo-core-tag {
|
||||
@@ -281,7 +286,11 @@
|
||||
border: 1px solid #333333ce;
|
||||
color: var(--text);
|
||||
background: #99999930;
|
||||
white-space:nowrap;
|
||||
white-space: wrap;
|
||||
word-wrap: break-word;
|
||||
hyphens: auto;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.pexpo-core-desc {
|
||||
@@ -314,6 +323,45 @@
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
#pexpo-core-pagination {
|
||||
margin-top: 20px;
|
||||
display:flex;
|
||||
justify-content:center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
#pexpo-core-pagination button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: 0 8px 22px var(--stroke2);
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
cursor:pointer;
|
||||
|
||||
&:focus-visible, &:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
#pexpo-core-page-number {
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Filter drawer */
|
||||
.pexpo-core-filterDrawerBackdrop {
|
||||
position:absolute;
|
||||
@@ -363,7 +411,7 @@
|
||||
.pexpo-core-filterPanelHeader {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
justify-content:center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid var(--stroke);
|
||||
color: rgba(16,24,40,.60);
|
||||
@@ -377,7 +425,7 @@
|
||||
padding: 10px;
|
||||
flex-direction: column;
|
||||
overflow:auto;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -398,9 +446,8 @@
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pexpo-core-toggle label::before {
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -410,9 +457,9 @@
|
||||
border: 1.5px solid color-mix(in srgb, var(--toggle-bg-off), black var(--darker));
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.pexpo-core-toggle label::after {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 21px;
|
||||
@@ -424,6 +471,7 @@
|
||||
left: 2px;
|
||||
transition: all 0.2s ease-in;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.pexpo-core-toggle input:checked + label::before {
|
||||
@@ -435,6 +483,17 @@
|
||||
left: 27px;
|
||||
}
|
||||
|
||||
#pexpo-core-filterApply {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: var(--accent1);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* .pexpo-core-textarea {
|
||||
width: 100%;
|
||||
height: 210px;
|
||||
@@ -456,6 +515,12 @@
|
||||
box-shadow: 0 0 0 3px rgba(44,52,137,.14);
|
||||
} */
|
||||
|
||||
#pexpo-core-emptyResult {
|
||||
padding:20px;
|
||||
color:var(--text);
|
||||
display:none;
|
||||
}
|
||||
|
||||
.pexpo-core-hint {
|
||||
margin-top: 8px;
|
||||
color: rgba(16,24,40,.60);
|
||||
@@ -569,6 +634,70 @@
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.pexpo-core-icons-arrow-left {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: scale(var(--ggs, 1));
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.pexpo-core-icons-arrow-left::after,
|
||||
.pexpo-core-icons-arrow-left::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
}
|
||||
.pexpo-core-icons-arrow-left::after {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-bottom: 2px solid;
|
||||
border-left: 2px solid;
|
||||
transform: rotate(45deg);
|
||||
bottom: 7px;
|
||||
}
|
||||
.pexpo-core-icons-arrow-left::before {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
bottom: 10px;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.pexpo-core-icons-arrow-right {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: scale(var(--ggs, 1));
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.pexpo-core-icons-arrow-right::after,
|
||||
.pexpo-core-icons-arrow-right::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
}
|
||||
.pexpo-core-icons-arrow-right::after {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top: 2px solid;
|
||||
border-right: 2px solid;
|
||||
transform: rotate(45deg);
|
||||
bottom: 7px;
|
||||
}
|
||||
.pexpo-core-icons-arrow-right::before {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
bottom: 10px;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.pexpo-core-topbar { flex-direction:column; }
|
||||
.pexpo-core-searchWrap { justify-content:stretch; }
|
||||
|
||||
@@ -1,62 +1,51 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ---------- DOM Elements ----------
|
||||
const shell = document.getElementById("pexpo-core-shell");
|
||||
const topbar = document.getElementById("pexpo-core-topbar");
|
||||
// We instantiate the class when the DOM is ready
|
||||
new PartnerExpoSearch();
|
||||
});
|
||||
|
||||
const filterWrap = document.getElementById("pexpo-core-filterWrap");
|
||||
const filterBtn = document.getElementById("pexpo-core-filterBtn");
|
||||
const filterDrawer = document.getElementById("pexpo-core-filterDrawer");
|
||||
const drawerBackdrop = document.getElementById("pexpo-core-drawerBackdrop");
|
||||
class PartnerExpoSearch {
|
||||
constructor() {
|
||||
this.els = {
|
||||
shell: document.getElementById("pexpo-core-shell"),
|
||||
topbar: document.getElementById("pexpo-core-topbar"),
|
||||
filterWrap: document.getElementById("pexpo-core-filterWrap"),
|
||||
filterBtn: document.getElementById("pexpo-core-filterBtn"),
|
||||
filterDrawer: document.getElementById("pexpo-core-filterDrawer"),
|
||||
drawerBackdrop: document.getElementById("pexpo-core-drawerBackdrop"),
|
||||
filterFTag: document.getElementById("pexpo-core-tag-toggle"),
|
||||
filterTags: document.getElementById("pexpo-core-tags"),
|
||||
filterCompanies: document.getElementById("pexpo-core-companies"),
|
||||
qInput: document.getElementById("pexpo-core-q"),
|
||||
masonry: document.getElementById("pexpo-core-masonry"),
|
||||
measure: document.getElementById("pexpo-core-measure"),
|
||||
emptyResult: document.getElementById("pexpo-core-emptyResult"),
|
||||
count: document.getElementById("pexpo-core-count"),
|
||||
layoutMeta: document.getElementById("pexpo-core-layoutMeta"),
|
||||
sortSelect: document.getElementById("pexpo-core-sortSelect"),
|
||||
filterApply: document.getElementById("pexpo-core-filterApply"),
|
||||
pageNum: document.getElementById("pexpo-core-page-number"),
|
||||
pagePrev: document.getElementById("pexpo-core-page-prev"),
|
||||
pageNext: document.getElementById("pexpo-core-page-next"),
|
||||
};
|
||||
|
||||
// const filterBody = document.getElementById("pexpo-core-filterBody");
|
||||
// const filterLabel = document.getElementById("pexpo-core-filterLabel");
|
||||
const filterFTag = document.getElementById("pexpo-core-tag-toggle");
|
||||
const filterTags = document.getElementById("pexpo-core-tags");
|
||||
|
||||
const qInput = document.getElementById("pexpo-core-q");
|
||||
const masonry = document.getElementById("pexpo-core-masonry");
|
||||
const measure = document.getElementById("pexpo-core-measure");
|
||||
|
||||
const count = document.getElementById("pexpo-core-count");
|
||||
const layoutMeta = document.getElementById("pexpo-core-layoutMeta");
|
||||
const sortSelect = document.getElementById("pexpo-core-sortSelect");
|
||||
|
||||
// ---------- State ----------
|
||||
let filters = {
|
||||
this.state = {
|
||||
filters: {
|
||||
q: "",
|
||||
sort: "relevance",
|
||||
force_tags: false,
|
||||
tags: [],
|
||||
companies: [],
|
||||
resultsPerPage: 20,
|
||||
page: 1,
|
||||
},
|
||||
activeSortKey: "relevance",
|
||||
total: '',
|
||||
pages: 1,
|
||||
results: [],
|
||||
layoutQueued: false
|
||||
};
|
||||
|
||||
let activeSortKey = "relevance";
|
||||
let results = [];
|
||||
|
||||
// ---------- Initialize MultiSelect Library ----------
|
||||
if (typeof MultiSelect !== 'undefined') {
|
||||
new MultiSelect(filterTags, {
|
||||
// max: 20,
|
||||
search: true,
|
||||
selectAll: false,
|
||||
onSelect: function(value) {
|
||||
if (!filters.tags.includes(value)) {
|
||||
filters.tags.push(value);
|
||||
}
|
||||
console.log('Updated filters.tags:', filters.tags);
|
||||
},
|
||||
onUnselect: function(value) {
|
||||
filters.tags = filters.tags.filter(tag => tag !== value);
|
||||
console.log('Updated filters.tags:', filters.tags);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn("MultiSelect library not found. Filters will not work without it.");
|
||||
}
|
||||
|
||||
// ---------- Sort Definitions ----------
|
||||
const SORTS = {
|
||||
this.SORTS = {
|
||||
relevance: "Relevánsság (legjobb → legrosszabb)",
|
||||
date_desc: "Dátum (új → régi)",
|
||||
date_asc: "Dátum (régi → új)",
|
||||
@@ -64,277 +53,366 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
title_desc: "Cím (Z → A)",
|
||||
};
|
||||
|
||||
// ---------- Helpers ----------
|
||||
function escapeHtml(str) {
|
||||
return String(str).replace(/[&<>"']/g, s => ({
|
||||
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
||||
}[s]));
|
||||
}
|
||||
|
||||
function encodeDataToURL(data) {
|
||||
return Object
|
||||
.keys(data)
|
||||
.map(value => `${value}=${encodeURIComponent(data[value])}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
function cardEl(r) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "pexpo-core-card";
|
||||
div.innerHTML = `
|
||||
<div class="pexpo-core-imageWrap">
|
||||
<img src="${escapeHtml(r.image || '')}"
|
||||
alt="${escapeHtml(r.title)}"
|
||||
style="width:100%; border-radius:10px; object-fit:cover;" />
|
||||
</div>
|
||||
<div class="pexpo-core-cardTop">
|
||||
<a href="${escapeHtml(r.url)}" class="pexpo-core-title">${escapeHtml(r.title)}</a>
|
||||
</div>
|
||||
<div class="pexpo-core-desc">${escapeHtml(r.excerpt)}</div>
|
||||
<div class="pexpo-core-foot">
|
||||
<div class="pexpo-core-tags">
|
||||
${
|
||||
r.tag != null
|
||||
? r.tag.map(element => `<span class="pexpo-core-tag">${escapeHtml(element)}</span>`).join('')
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
<div>${escapeHtml(r.date)}</div>
|
||||
</div>
|
||||
`;
|
||||
return div;
|
||||
}
|
||||
|
||||
// ---------- Ajax Function ----------
|
||||
async function fetchData() {
|
||||
// 1. Show loading state if needed
|
||||
masonry.style.opacity = "0.5";
|
||||
|
||||
try {
|
||||
const response = await fetch(`/wp-json/pexpo/v1/query?${encodeDataToURL(filters)}`);
|
||||
const data = await response.json();
|
||||
|
||||
results = data;
|
||||
|
||||
requestLayout();
|
||||
|
||||
} catch (error) {
|
||||
console.error("Search failed:", error);
|
||||
masonry.innerHTML = `<div style="padding:20px; color:red;">Hiba történt a keresés során.</div>`;
|
||||
} finally {
|
||||
masonry.style.opacity = "1";
|
||||
if (this.els.shell) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Layout & Masonry Logic ----------
|
||||
const minCard = () => Number(getComputedStyle(document.documentElement).getPropertyValue("--cardMin")) || 240;
|
||||
const gap = () => Number(getComputedStyle(document.documentElement).getPropertyValue("--gap")) || 12;
|
||||
init() {
|
||||
this.initMultiSelect();
|
||||
this.initSortDropdown();
|
||||
this.bindEvents();
|
||||
this.syncTopbarHeight();
|
||||
this.initResizeObserver();
|
||||
|
||||
let layoutQueued = false;
|
||||
function requestLayout() {
|
||||
if (layoutQueued) return;
|
||||
layoutQueued = true;
|
||||
requestAnimationFrame(() => {
|
||||
layoutQueued = false;
|
||||
applyMasonry();
|
||||
// Initial Fetch
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
// ---------- Initialization Helpers ----------
|
||||
|
||||
initMultiSelect() {
|
||||
if (typeof MultiSelect !== 'undefined') {
|
||||
new MultiSelect(this.els.filterTags, {
|
||||
search: true,
|
||||
selectAll: false,
|
||||
onSelect: (value) => {
|
||||
if (!this.state.filters.tags.includes(value)) {
|
||||
this.state.filters.tags.push(value);
|
||||
}
|
||||
},
|
||||
onUnselect: (value) => {
|
||||
this.state.filters.tags = this.state.filters.tags.filter(tag => tag !== value);
|
||||
}
|
||||
});
|
||||
|
||||
new MultiSelect(this.els.filterCompanies, {
|
||||
search: true,
|
||||
selectAll: false,
|
||||
onSelect: (value) => {
|
||||
if (!this.state.filters.companies.includes(value)) {
|
||||
this.state.filters.companies.push(value);
|
||||
}
|
||||
},
|
||||
onUnselect: (value) => {
|
||||
this.state.filters.companies = this.state.filters.companies.filter(company => company !== value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn("MultiSelect library not found.");
|
||||
}
|
||||
}
|
||||
|
||||
initSortDropdown() {
|
||||
if (!this.els.sortSelect) return;
|
||||
this.els.sortSelect.innerHTML = Object.entries(this.SORTS)
|
||||
.map(([k, label]) => `<option value="${this.escapeHtml(k)}">${this.escapeHtml(label)}</option>`)
|
||||
.join("");
|
||||
this.els.sortSelect.value = this.state.activeSortKey;
|
||||
}
|
||||
|
||||
initResizeObserver() {
|
||||
const ro = new ResizeObserver(() => {
|
||||
this.syncTopbarHeight();
|
||||
this.requestLayout();
|
||||
});
|
||||
ro.observe(this.els.shell);
|
||||
ro.observe(this.els.masonry);
|
||||
}
|
||||
|
||||
// ---------- Event Binding ----------
|
||||
|
||||
bindEvents() {
|
||||
// Sort
|
||||
if (this.els.sortSelect) {
|
||||
this.els.sortSelect.addEventListener("change", () => {
|
||||
this.state.activeSortKey = this.els.sortSelect.value;
|
||||
this.state.filters.sort = this.state.activeSortKey;
|
||||
this.fetchData();
|
||||
});
|
||||
}
|
||||
|
||||
function getColumnCount() {
|
||||
const w = masonry.clientWidth || masonry.getBoundingClientRect().width || 1;
|
||||
const mc = minCard();
|
||||
const g = gap();
|
||||
// Search
|
||||
this.els.qInput.addEventListener("keyup", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
this.state.filters.q = this.els.qInput.value.trim();
|
||||
this.fetchData();
|
||||
}
|
||||
});
|
||||
|
||||
// Tag Toggle
|
||||
this.els.filterFTag.addEventListener("change", () => {
|
||||
this.state.filters.force_tags = this.els.filterFTag.checked;
|
||||
});
|
||||
|
||||
// Drawer Toggle
|
||||
this.els.filterBtn.addEventListener("click", () => {
|
||||
const isOpen = this.els.filterDrawer.classList.contains("pexpo-core-open");
|
||||
this.setDrawerOpen(!isOpen);
|
||||
});
|
||||
|
||||
this.els.filterApply.addEventListener("click", () => {
|
||||
this.fetchData();
|
||||
this.setDrawerOpen(false);
|
||||
});
|
||||
|
||||
this.els.drawerBackdrop.addEventListener("click", () => this.setDrawerOpen(false));
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && this.els.filterDrawer.classList.contains("pexpo-core-open")) {
|
||||
this.setDrawerOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.els.pageNext.addEventListener("click", () => {
|
||||
if (this.state.filters.page < this.state.pages) {
|
||||
this.state.filters.page += 1;
|
||||
this.fetchData();
|
||||
}
|
||||
});
|
||||
|
||||
this.els.pagePrev.addEventListener("click", () => {
|
||||
if (this.state.filters.page > 1) {
|
||||
this.state.filters.page -= 1;
|
||||
this.fetchData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Fetch ----------
|
||||
|
||||
async fetchData() {
|
||||
this.els.masonry.style.opacity = "0.5";
|
||||
|
||||
try {
|
||||
const queryString = this.encodeDataToURL(this.state.filters);
|
||||
const response = await fetch(`/wp-json/pexpo/v1/query?${queryString}`);
|
||||
const data = await response.json();
|
||||
|
||||
this.state.results = data['results'] || [];
|
||||
this.state.total = data['found'] || 0;
|
||||
this.state.pages = data['pages'] || 1;
|
||||
console.log("Fetched data:", data['pages']);
|
||||
this.requestLayout();
|
||||
|
||||
} catch (error) {
|
||||
console.error("Search failed:", error);
|
||||
this.els.masonry.innerHTML = `<div style="padding:20px; color:red;">Hiba történt a keresés során.</div>`;
|
||||
} finally {
|
||||
this.els.masonry.style.opacity = "1";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- UI & Layout Methods ----------
|
||||
|
||||
setDrawerOpen(open) {
|
||||
this.els.filterWrap.classList.toggle("pexpo-core-open", open);
|
||||
this.els.filterBtn.setAttribute("aria-expanded", String(open));
|
||||
|
||||
this.els.filterDrawer.classList.toggle("pexpo-core-open", open);
|
||||
this.els.filterDrawer.setAttribute("aria-hidden", String(!open));
|
||||
|
||||
this.els.drawerBackdrop.classList.toggle("pexpo-core-show", open);
|
||||
this.els.shell.classList.toggle("pexpo-core-drawerOpen", open);
|
||||
|
||||
this.syncTopbarHeight();
|
||||
}
|
||||
|
||||
syncTopbarHeight() {
|
||||
const h = this.els.topbar.offsetHeight;
|
||||
document.documentElement.style.setProperty("--topbarH", h + "px");
|
||||
}
|
||||
|
||||
// ---------- Masonry Engine ----------
|
||||
|
||||
minCard() {
|
||||
return Number(getComputedStyle(document.documentElement).getPropertyValue("--cardMin")) || 240;
|
||||
}
|
||||
|
||||
gap() {
|
||||
return Number(getComputedStyle(document.documentElement).getPropertyValue("--gap")) || 12;
|
||||
}
|
||||
|
||||
requestLayout() {
|
||||
if (this.state.layoutQueued) return;
|
||||
this.state.layoutQueued = true;
|
||||
requestAnimationFrame(() => {
|
||||
this.state.layoutQueued = false;
|
||||
this.applyMasonry();
|
||||
});
|
||||
}
|
||||
|
||||
getColumnCount() {
|
||||
const w = this.els.masonry.clientWidth || this.els.masonry.getBoundingClientRect().width || 1;
|
||||
const mc = this.minCard();
|
||||
const g = this.gap();
|
||||
return Math.max(1, Math.floor((w + g) / (mc + g)));
|
||||
}
|
||||
|
||||
function measureCardHeights(cols, list) {
|
||||
const w = masonry.clientWidth || 1;
|
||||
const g = gap();
|
||||
measureCardHeights(cols, list) {
|
||||
const w = this.els.masonry.clientWidth || 1;
|
||||
const g = this.gap();
|
||||
const colW = Math.floor((w - (cols - 1) * g) / cols);
|
||||
measure.style.width = colW + "px";
|
||||
measure.innerHTML = "";
|
||||
|
||||
this.els.measure.style.width = colW + "px";
|
||||
this.els.measure.innerHTML = "";
|
||||
|
||||
const measured = list.map((r, idx) => {
|
||||
const el = cardEl(r);
|
||||
measure.appendChild(el);
|
||||
const el = this.createCardElement(r);
|
||||
this.els.measure.appendChild(el);
|
||||
const h = el.offsetHeight;
|
||||
measure.removeChild(el);
|
||||
this.els.measure.removeChild(el);
|
||||
return { r, idx, h };
|
||||
});
|
||||
|
||||
return { measured, colW };
|
||||
}
|
||||
|
||||
function packRankAware(measured, cols, K = 6) {
|
||||
packStandard(measured, cols) {
|
||||
// 1. Create an array to track the current height of each column
|
||||
const colHeights = new Array(cols).fill(0);
|
||||
const placements = [];
|
||||
const queue = measured.slice();
|
||||
|
||||
while (queue.length) {
|
||||
// Find shortest column
|
||||
// 2. Prepare the array to store where each item goes
|
||||
const placements = [];
|
||||
|
||||
// 3. Loop through every item strictly in order
|
||||
for (const item of measured) {
|
||||
|
||||
// 4. Find the column that is currently the shortest
|
||||
let bestCol = 0;
|
||||
for (let c = 1; c < cols; c++) {
|
||||
if (colHeights[c] < colHeights[bestCol]) bestCol = c;
|
||||
if (colHeights[c] < colHeights[bestCol]) {
|
||||
bestCol = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Look at top K items, pick the one that fits best (tallest)
|
||||
// or just the next one if you prefer strict ordering.
|
||||
// Here we pick the tallest of the next K to fill gaps.
|
||||
const lookN = Math.min(K, queue.length);
|
||||
let pick = 0;
|
||||
for (let i = 1; i < lookN; i++) {
|
||||
if (queue[i].h > queue[pick].h) pick = i;
|
||||
// 5. Place the item there
|
||||
placements.push({ item: item, col: bestCol });
|
||||
|
||||
// 6. Update that column's height
|
||||
colHeights[bestCol] += item.h + this.gap();
|
||||
}
|
||||
|
||||
const picked = queue.splice(pick, 1)[0];
|
||||
placements.push({ item: picked, col: bestCol });
|
||||
colHeights[bestCol] += picked.h + gap();
|
||||
}
|
||||
return placements;
|
||||
}
|
||||
|
||||
function applyMasonry() {
|
||||
const n = results.length || 0;
|
||||
count.textContent = n;
|
||||
masonry.innerHTML = "";
|
||||
// packRankAware(measured, cols, K = 6) {
|
||||
// const colHeights = new Array(cols).fill(0);
|
||||
// const placements = [];
|
||||
// const queue = measured.slice();
|
||||
|
||||
const cols = getColumnCount();
|
||||
// while (queue.length) {
|
||||
// // Find shortest column
|
||||
// let bestCol = 0;
|
||||
// for (let c = 1; c < cols; c++) {
|
||||
// if (colHeights[c] < colHeights[bestCol]) bestCol = c;
|
||||
// }
|
||||
|
||||
// Create Columns with pexpo-core class
|
||||
// const lookN = Math.min(K, queue.length);
|
||||
// let pick = 0;
|
||||
// for (let i = 1; i < lookN; i++) {
|
||||
// if (queue[i].h > queue[pick].h) pick = i;
|
||||
// }
|
||||
|
||||
// const picked = queue.splice(pick, 1)[0];
|
||||
// placements.push({ item: picked, col: bestCol });
|
||||
// colHeights[bestCol] += picked.h + this.gap();
|
||||
// }
|
||||
// return placements;
|
||||
// }
|
||||
|
||||
applyMasonry() {
|
||||
const n = this.state.results.length || 0;
|
||||
const page = this.state.filters.page || 1;
|
||||
const pages = this.state.pages;
|
||||
this.els.count.textContent = this.state.total ? this.state.total : '';
|
||||
this.els.pageNum.textContent = page;
|
||||
this.els.masonry.innerHTML = "";
|
||||
|
||||
if (page >= pages) {
|
||||
this.els.pageNext.setAttribute("disabled", "disabled");
|
||||
} else {
|
||||
this.els.pageNext.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
if (page <= 1) {
|
||||
this.els.pagePrev.setAttribute("disabled", "disabled");
|
||||
} else {
|
||||
this.els.pagePrev.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
if(this.els.emptyResult) {
|
||||
this.els.count.textContent = this.state.total ? this.state.total : 0;
|
||||
let emptyClone = this.els.emptyResult.cloneNode(true);
|
||||
emptyClone.style.display = "block";
|
||||
this.els.masonry.appendChild(emptyClone);
|
||||
} else {
|
||||
this.els.masonry.innerHTML = `<div style="padding:20px; color:#666;">Nincs találat.</div>`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const cols = this.getColumnCount();
|
||||
const colEls = [];
|
||||
|
||||
// Create Columns
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const col = document.createElement("div");
|
||||
col.className = "pexpo-core-mCol";
|
||||
masonry.appendChild(col);
|
||||
this.els.masonry.appendChild(col);
|
||||
colEls.push(col);
|
||||
}
|
||||
|
||||
const sliced = results.slice();
|
||||
const sliced = this.state.results.slice();
|
||||
const { measured } = this.measureCardHeights(cols, sliced);
|
||||
// const placements = this.packRankAware(measured, cols, 6);
|
||||
const placements = this.packStandard(measured, cols);
|
||||
|
||||
// Measure & Pack
|
||||
const { measured } = measureCardHeights(cols, sliced);
|
||||
const placements = packRankAware(measured, cols, 6);
|
||||
|
||||
// Render
|
||||
for (const p of placements) {
|
||||
colEls[p.col].appendChild(cardEl(p.item.r));
|
||||
colEls[p.col].appendChild(this.createCardElement(p.item.r));
|
||||
}
|
||||
}
|
||||
|
||||
// layoutMeta.textContent = `${cols} oszlop • ${SORTS[activeSortKey]?.label || "Relevance"}`;
|
||||
createCardElement(r) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "pexpo-core-card";
|
||||
div.innerHTML = `
|
||||
<div class="pexpo-core-imageWrap">
|
||||
<img src="${this.escapeHtml(r.image || '')}"
|
||||
alt="${this.escapeHtml(r.title)}"
|
||||
style="width:100%; border-radius:10px; object-fit:cover;" />
|
||||
</div>
|
||||
<div class="pexpo-core-cardTop">
|
||||
<a href="${this.escapeHtml(r.url)}" class="pexpo-core-title">${this.escapeHtml(r.title)}</a>
|
||||
</div>
|
||||
<div class="pexpo-core-desc">${this.escapeHtml(r.excerpt)}</div>
|
||||
<div class="pexpo-core-foot">
|
||||
<div class="pexpo-core-tags">
|
||||
${
|
||||
Array.isArray(r.tag)
|
||||
? r.tag.map(element => `<span class="pexpo-core-tag">${this.escapeHtml(element)}</span>`).join('')
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
<div>${this.escapeHtml(r.date)}</div>
|
||||
</div>
|
||||
`;
|
||||
return div;
|
||||
}
|
||||
|
||||
// ---------- UI Events ----------
|
||||
// ---------- Utilities ----------
|
||||
|
||||
// Sort Dropdown
|
||||
if (sortSelect) {
|
||||
sortSelect.innerHTML = Object.entries(SORTS)
|
||||
.map(([k, label]) => `<option value="${escapeHtml(k)}">${escapeHtml(label)}</option>`)
|
||||
.join("");
|
||||
sortSelect.value = activeSortKey;
|
||||
sortSelect.addEventListener("change", () => {
|
||||
activeSortKey = sortSelect.value;
|
||||
filters.sort = activeSortKey;
|
||||
fetchData();
|
||||
});
|
||||
escapeHtml(str) {
|
||||
return String(str).replace(/[&<>"']/g, s => ({
|
||||
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
||||
}[s]));
|
||||
}
|
||||
|
||||
// Search
|
||||
qInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
filters.q = qInput.value.trim();
|
||||
fetchData();
|
||||
encodeDataToURL(data) {
|
||||
return Object
|
||||
.keys(data)
|
||||
.map(value => `${value}=${encodeURIComponent(data[value])}`)
|
||||
.join('&');
|
||||
}
|
||||
});
|
||||
|
||||
// Tag Toggle
|
||||
filterFTag.addEventListener("change", () => {
|
||||
filters.force_tags = filterFTag.checked;
|
||||
});
|
||||
|
||||
// Tags multiselect
|
||||
filterTags.addEventListener("change", (value, text, element) => {
|
||||
console.log('Change:', value, text, element);
|
||||
});
|
||||
|
||||
|
||||
// Filter Drawer Logic
|
||||
function syncTopbarHeight() {
|
||||
const h = topbar.offsetHeight;
|
||||
document.documentElement.style.setProperty("--topbarH", h + "px");
|
||||
}
|
||||
|
||||
function setOpen(open) {
|
||||
filterWrap.classList.toggle("pexpo-core-open", open); // Updated class
|
||||
filterBtn.setAttribute("aria-expanded", String(open));
|
||||
|
||||
filterDrawer.classList.toggle("pexpo-core-open", open); // Updated class
|
||||
filterDrawer.setAttribute("aria-hidden", String(!open));
|
||||
|
||||
drawerBackdrop.classList.toggle("pexpo-core-show", open); // Updated class
|
||||
shell.classList.toggle("pexpo-core-drawerOpen", open);
|
||||
|
||||
syncTopbarHeight();
|
||||
}
|
||||
|
||||
filterBtn.addEventListener("click", () => setOpen(!filterDrawer.classList.contains("pexpo-core-open")));
|
||||
drawerBackdrop.addEventListener("click", () => setOpen(false));
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && filterDrawer.classList.contains("pexpo-core-open")) setOpen(false);
|
||||
});
|
||||
|
||||
// // Apply JSON Filter
|
||||
// function applyJson() {
|
||||
// try {
|
||||
// const parsed = JSON.parse(filterJson.value);
|
||||
// jsonError.classList.remove("pexpo-core-show");
|
||||
// filters = parsed;
|
||||
// filterLabel.textContent = (filters.category || "Custom");
|
||||
|
||||
// // Trigger new search with new filters
|
||||
// filters.q = qInput.value.trim();
|
||||
// fetchData();
|
||||
|
||||
// // Close drawer on success (optional)
|
||||
// // setOpen(false);
|
||||
// } catch (err) {
|
||||
// jsonError.classList.add("pexpo-core-show");
|
||||
// }
|
||||
// }
|
||||
|
||||
// filterJson.addEventListener("keydown", (e) => {
|
||||
// if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
// e.preventDefault();
|
||||
// applyJson();
|
||||
// }
|
||||
// });
|
||||
|
||||
// Resize Observer
|
||||
const ro = new ResizeObserver(() => {
|
||||
syncTopbarHeight();
|
||||
requestLayout();
|
||||
});
|
||||
ro.observe(shell);
|
||||
ro.observe(masonry);
|
||||
|
||||
// Initial Load
|
||||
initFilters();
|
||||
syncTopbarHeight();
|
||||
fetchData();
|
||||
|
||||
// --- Temporary Mock Response Generator (DELETE ME IN PRODUCTION) ---
|
||||
function simulateBackendResponse(q) {
|
||||
const count = filters.maxResults || 12;
|
||||
const arr = [];
|
||||
for(let i=0; i<count; i++) {
|
||||
arr.push({
|
||||
id: i,
|
||||
title: `Item ${i} - ${q || 'Random'}`,
|
||||
desc: "This is a placeholder description returned from the fake backend.",
|
||||
score: Math.floor(Math.random()*100),
|
||||
updated: new Date().toISOString().slice(0,10),
|
||||
relevance: 0
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9,23 +9,49 @@
|
||||
* @package Partnerexpo_Core
|
||||
* @subpackage Partnerexpo_Core/public/partials
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="pexpo-core-root pexpo-core-shell" id="shell">
|
||||
<div class="pexpo-core-topbar" id="topbar">
|
||||
<div class="pexpo-core-filterWrap" id="filterWrap">
|
||||
<div class="pexpo-core-filterBtn pexpo-core-topHeight" id="filterBtn" role="button" aria-expanded="false" tabindex="0">
|
||||
$tags = get_terms([
|
||||
'taxonomy' => 'pexpo_tags',
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
|
||||
$users = get_users([
|
||||
'meta_key' => 'company',
|
||||
'meta_compare' => 'EXISTS',
|
||||
]);
|
||||
|
||||
$companies = [];
|
||||
foreach ($users as $user) {
|
||||
$company = get_user_meta($user->ID, 'company', true);
|
||||
if (!empty($company)) {
|
||||
$companies[$company] = $company;
|
||||
}
|
||||
}
|
||||
|
||||
$authors = get_users([
|
||||
'meta_key' => 'company',
|
||||
'meta_value' => array_keys($companies) ?? [],
|
||||
'meta_compare' => 'IN',
|
||||
'fields' => 'ID',
|
||||
]);
|
||||
|
||||
|
||||
?>
|
||||
<div class="pexpo-core-root pexpo-core-shell" id="pexpo-core-shell">
|
||||
<div class="pexpo-core-topbar" id="pexpo-core-topbar">
|
||||
<div class="pexpo-core-filterWrap" id="pexpo-core-filterWrap">
|
||||
<div class="pexpo-core-filterBtn pexpo-core-topHeight" id="pexpo-core-filterBtn" role="button" aria-expanded="false" tabindex="0">
|
||||
<div class="pexpo-core-left">
|
||||
<span class="pexpo-core-icons-options" aria-hidden="true"></span>
|
||||
<span class="pexpo-core-label" id="filterLabel">Filterek</span>
|
||||
<span class="pexpo-core-label" id="pexpo-core-filterLabel"><?php echo esc_html__('Szűrők', 'partnerexpo-core'); ?></span>
|
||||
</div>
|
||||
<span class="pexpo-core-icons-down" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pexpo-core-sortWrap pexpo-core-topHeight">
|
||||
<label class="pexpo-core-sortLabel" for="sortSelect">Rendezés</label>
|
||||
<select id="sortSelect" class="pexpo-core-sortSelect">
|
||||
<label class="pexpo-core-sortLabel" for="pexpo-core-sortSelect"><?php echo esc_html__('Rendezés', 'partnerexpo-core'); ?></label>
|
||||
<select id="pexpo-core-sortSelect" class="pexpo-core-sortSelect">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -33,41 +59,55 @@
|
||||
<div class="pexpo-core-search" role="search">
|
||||
<span class="pexpo-core-icons-search" aria-hidden="true"></span>
|
||||
<form id="pexpo-core-qForm" action="javascript:void(0)">
|
||||
<input id="pexpo-core-q" placeholder="Keresés..." autocomplete="off" />
|
||||
<input id="pexpo-core-q" placeholder="<?php echo esc_attr__('Keresés...', 'partnerexpo-core'); ?>" autocomplete="off" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pexpo-core-filterDrawerBackdrop pexpo-core-show " id="pexpo-core-drawerBackdrop" aria-hidden="true"></div>
|
||||
<div class="pexpo-core-filterDrawerBackdrop " id="pexpo-core-drawerBackdrop" aria-hidden="true"></div>
|
||||
<div class="pexpo-core-filterDrawer" id="pexpo-core-filterDrawer" aria-hidden="true">
|
||||
<div class="pexpo-core-filterPanelHeader">
|
||||
<span><b>Filters</b></span>
|
||||
<span><b><?php echo esc_html__('Szűrők', 'partnerexpo-core'); ?></b></span>
|
||||
</div>
|
||||
<div class="pexpo-core-filterPanelBody">
|
||||
<div class="pexpo-core-toggle">
|
||||
<span class="toggle-text">Címke kötelezése</span>
|
||||
<span class="toggle-text"><?php echo esc_html__('Címke kötelezése', 'partnerexpo-core'); ?></span>
|
||||
<div class="toggle-wrapper">
|
||||
<input type="checkbox" id="pexpo-core-tag-toggle">
|
||||
<label for="pexpo-core-tag-toggle"></label>
|
||||
</div>
|
||||
</div>
|
||||
<label for="pexpo-core-tags">Címkék</label>
|
||||
<select id="pexpo-core-tags" data-placeholder="Opciók kiválasztása" multiple="multiple">
|
||||
<!-- <option value="option1">Option 1</option>
|
||||
<option value="option2">Option 2</option>
|
||||
<option value="option3">Option 3</option> -->
|
||||
<label for="pexpo-core-tags"><?php echo esc_html__('Címkék', 'partnerexpo-core'); ?></label>
|
||||
<select id="pexpo-core-tags" data-placeholder="<?php echo esc_attr__('Címkék kiválasztása', 'partnerexpo-core'); ?>" data-search-text="<?php echo esc_attr__('Keresés...', 'partnerexpo-core'); ?>" multiple="multiple">
|
||||
<?php foreach ($tags as $tag) : ?>
|
||||
<option value="<?php echo esc_attr($tag->slug); ?>"><?php echo esc_html($tag->name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label for="pexpo-core-companies"><?php echo esc_html__('Cégek', 'partnerexpo-core'); ?></label>
|
||||
<select id="pexpo-core-companies" data-placeholder="<?php echo esc_attr__('Cégek kiválasztása', 'partnerexpo-core'); ?>" data-search-text="<?php echo esc_attr__('Keresés...', 'partnerexpo-core'); ?>" multiple="multiple">
|
||||
<?php foreach ($companies as $key => $company) : ?>
|
||||
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html($company); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button id="pexpo-core-filterApply"><?php echo esc_html__('Alkalmaz', 'partnerexpo-core'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pexpo-core-body">
|
||||
<div class="pexpo-core-metaRow">
|
||||
<div id="pexpo-core-metaLeft"><b id="pexpo-core-count">0</b> találat</div>
|
||||
<div id="pexpo-core-metaRight">Elrendezés: <span id="pexpo-core-layoutMeta">—</span></div>
|
||||
<div id="pexpo-core-metaLeft"><b id="pexpo-core-count"></b> <?php echo esc_html__('találat', 'partnerexpo-core'); ?></div>
|
||||
</div>
|
||||
|
||||
<div class="pexpo-core-masonry" id="pexpo-core-masonry" aria-live="polite"></div>
|
||||
<div class="pexpo-core-masonry" id="pexpo-core-masonry" aria-live="polite">
|
||||
|
||||
</div>
|
||||
<div id="pexpo-core-pagination">
|
||||
<button id="pexpo-core-page-prev"><span class="pexpo-core-icons-arrow-left" ></span></button>
|
||||
<span id="pexpo-core-page-number">1</span>
|
||||
<button id="pexpo-core-page-next"><span class="pexpo-core-icons-arrow-right" ></span></button>
|
||||
</div>
|
||||
<div id="pexpo-core-emptyResult"><?php echo esc_html__('Nincs a szűrőnek megfelelő találat.', 'partnerexpo-core'); ?></div>
|
||||
<div id="pexpo-core-measure" style="position:absolute; left:-9999px; top:-9999px; width:300px; visibility:hidden;"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,7 +6,6 @@
|
||||
<title>Mock Search UI (Masonry)</title>
|
||||
<link rel="stylesheet" href="css/multiselect.css">
|
||||
<style>
|
||||
/* [Core CSS - Same as before] */
|
||||
.pexpo-core-root { --accent1: #950000; --accent2: #2c3489; --darker: 30%; --lighter: 30%; --bg: #f6f7fb; --panel: #ffffff; --panel2: #f2f4fb; --stroke: rgba(16, 24, 40, .10); --stroke2: rgba(16, 24, 40, .14); --text: rgba(16, 24, 40, .92); --muted: rgba(16, 24, 40, .62); --shadow: 0 18px 45px rgba(16,24,40,.12); --r: 14px; --cardMin: 240; --gap: 12px; --filterW: 170px; --filterWOpen: 360px; --toggle-bg-off: #ca0000; --toggle-bg-on: #10bb2d; --toggle-nub-color: #f6f7fb; --topbarH: 64px; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji"; color: var(--text); line-height: 1.35; font-size: 14px; }
|
||||
.pexpo-core-root * { box-sizing: border-box; }
|
||||
.pexpo-core-shell { width: min(1100px, 96vw); height: min(720px, 92vh); background: linear-gradient(180deg, rgba(44,52,137,.05), transparent 42%), var(--panel); border: 1px solid var(--stroke); border-radius: calc(var(--r) + 2px); box-shadow: var(--shadow); overflow: hidden; display:flex; flex-direction:column; position: relative; isolation: isolate; }
|
||||
|
||||
Reference in New Issue
Block a user