diff --git a/test b/.gitignore similarity index 83% rename from test rename to .gitignore index 76e579a..9daeafb 100644 --- a/test +++ b/.gitignore @@ -1,2 +1 @@ test - diff --git a/includes/class-partnerexpo-core.php b/includes/class-partnerexpo-core.php index db34d76..e322e02 100644 --- a/includes/class-partnerexpo-core.php +++ b/includes/class-partnerexpo-core.php @@ -78,6 +78,7 @@ class Partnerexpo_Core { $this->set_locale(); $this->define_admin_hooks(); $this->define_public_hooks(); + $this->define_cpts(); } @@ -167,7 +168,6 @@ class Partnerexpo_Core { * @access private */ 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' ); @@ -175,6 +175,29 @@ class Partnerexpo_Core { } + private function define_cpts() { + $args = array( + 'labels' => array( + 'name' => 'partners', + 'singular_name' => __('Partner', 'partnerexpo-core'), + 'menu_name' => __('Partnerek', 'partnerexpo-core'), + 'add_new' => __('Új partner hozzáadása', 'partnerexpo-core'), + 'add_new_item' => __('Új partner hozzáadása', 'partnerexpo-core'), + 'new_item' => __('Új partner', 'partnerexpo-core'), + 'edit_item' => __('Partner szerkesztése', 'partnerexpo-core'), + 'view_item' => __('Partner megtekintése', 'partnerexpo-core'), + 'all_items' => __('Összes partner', 'partnerexpo-core'), + ), + 'public' => true, + 'has_archive' => true, + 'show_in_rest' => true, + 'supports' => array( 'title', 'thumbnail', 'excerpt' ), + ); + + register_post_type( 'partners', $args ); + + } + /** * Run the loader to execute all of the hooks with WordPress. * diff --git a/public/class-partnerexpo-core-public.php b/public/class-partnerexpo-core-public.php index c757626..75d6fd2 100644 --- a/public/class-partnerexpo-core-public.php +++ b/public/class-partnerexpo-core-public.php @@ -5,17 +5,6 @@ * * @link https://github.com/Duskell * @since 1.0.0 - * - * @package Partnerexpo_Core - * @subpackage Partnerexpo_Core/public - */ - -/** - * The public-facing functionality of the plugin. - * - * Defines the plugin name, version, and two examples hooks for how to - * enqueue the public-facing stylesheet and JavaScript. - * * @package Partnerexpo_Core * @subpackage Partnerexpo_Core/public * @author Juhász Levente @@ -54,27 +43,26 @@ class Partnerexpo_Core_Public { } + private function searchbox_shortcode() { + global $searchbox_used; + $searchbox_used = true; + + ob_start(); + include plugin_dir_path( __FILE__ ) . 'partials/partnerexpo-core-public-searchbox.php'; + return ob_get_clean(); + } + /** * Register the stylesheets for the public-facing side of the site. * * @since 1.0.0 */ public function enqueue_styles() { + global $searchbox_used; - /** - * This function is provided for demonstration purposes only. - * - * An instance of this class should be passed to the run() function - * defined in Partnerexpo_Core_Loader as all of the hooks are defined - * in that particular class. - * - * The Partnerexpo_Core_Loader will then create the relationship - * between the defined hooks and the functions defined in this - * class. - */ - - wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/partnerexpo-core-public.css', array(), $this->version, 'all' ); - + if ( ! empty( $searchbox_used ) ) { + wp_enqueue_style( $this->plugin_name . '-searchbox-css', plugin_dir_url( __FILE__ ) . 'css/searchbox.css', [], $this->version, 'all' ); + } } /** @@ -83,21 +71,11 @@ class Partnerexpo_Core_Public { * @since 1.0.0 */ public function enqueue_scripts() { + global $searchbox_used; - /** - * This function is provided for demonstration purposes only. - * - * An instance of this class should be passed to the run() function - * defined in Partnerexpo_Core_Loader as all of the hooks are defined - * in that particular class. - * - * The Partnerexpo_Core_Loader will then create the relationship - * between the defined hooks and the functions defined in this - * class. - */ - - wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/partnerexpo-core-public.js', array( 'jquery' ), $this->version, false ); - + if ( ! empty( $searchbox_used ) ) { + wp_enqueue_script( $this->plugin_name . '-searchbox-js', plugin_dir_url( __FILE__ ) . 'js/searchbox.js', [], $this->version, true ); + } } } diff --git a/public/css/searchbox.css b/public/css/searchbox.css new file mode 100644 index 0000000..798c686 --- /dev/null +++ b/public/css/searchbox.css @@ -0,0 +1,517 @@ +.pexpo-core-root { + --accent1: #950000; + --accent2: #2c3489; + + --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; + + --topbarH: 64px; + + /* Basic font reset for the component only */ + 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; +} + +/* Scoped box-sizing reset */ +.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; +} + +/* --- Top row --- */ +.pexpo-core-topbar { + display:flex; + align-items:flex-start; + justify-content:space-between; + gap: 12px; + padding: 14px; + border-bottom: 1px solid var(--stroke); + background: linear-gradient(180deg, rgba(16,24,40,.02), rgba(16,24,40,.00)); + position: relative; + z-index: 3; +} + +.pexpo-core-topHeight { + height: 45px; +} + +/* Filter button */ +.pexpo-core-filterWrap { + width: var(--filterW); + transition: width 220ms ease; +} +.pexpo-core-filterWrap.pexpo-core-open { width: var(--filterWOpen); } + +.pexpo-core-filterBtn { + width: 100%; + display:flex; + align-items:center; + justify-content:space-between; + gap: 10px; + padding: 10px 10px; + border-radius: 12px; + border: 1px solid var(--stroke2); + background: var(--panel2); + color: var(--text); + cursor:pointer; + user-select:none; + box-shadow: 0 8px 22px rgba(16,24,40,.10); +} + +.pexpo-core-filterBtn .pexpo-core-left { + display:flex; + align-items:center; + gap: 10px; + min-width: 0; +} + +.pexpo-core-pill { + font-size: 12px; + padding: 3px 8px; + border: 1px solid rgba(44,52,137,.25); + border-radius: 999px; + color: rgba(44,52,137,.85); + background: rgba(44,52,137,.06); + white-space:nowrap; +} + +.pexpo-core-filterBtn .pexpo-core-label { + font-weight: 650; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Sort */ +.pexpo-core-sortWrap { + display:flex; + justify-content: space-between; + align-items:center; + gap: 10px; + padding: 10px 10px; + border-radius: 12px; + border: 1px solid var(--stroke2); + background: var(--panel2); + color: var(--text); + box-shadow: 0 8px 22px rgba(16,24,40,.10); + flex: 0 0 auto; +} + +.pexpo-core-sortLabel { + font-weight: 650; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pexpo-core-sortSelect { + border: 1px solid rgba(16,24,40,.16); + background: rgba(255,255,255,.95); + color: var(--text); + border-radius: 10px; + padding: 5px 10px; + outline: none; + font: inherit; +} + +.pexpo-core-sortSelect:focus { + border-color: rgba(44,52,137,.45); + box-shadow: 0 0 0 3px rgba(44,52,137,.14); +} + +/* Search */ +.pexpo-core-searchWrap { + flex: 1 1 auto; + display:flex; + justify-content:flex-end; + align-items:flex-start; +} + +.pexpo-core-search { + width: min(520px, 100%); + display:flex; + align-items:center; + gap: 10px; + padding: 10px 12px; + border-radius: 14px; + border: 1px solid var(--stroke2); + background: rgba(255,255,255,.9); + box-shadow: 0 8px 22px rgba(16,24,40,.10); +} + +.pexpo-core-search input { + width: 100%; + border: none; + outline: none; + background: transparent; + color: var(--text); + font-size: 14px; +} + +.pexpo-core-kbd { + color: rgba(16,24,40,.62); + border: 1px solid rgba(16,24,40,.16); + border-bottom-color: rgba(16,24,40,.22); + background: rgba(255,255,255,.85); + border-radius: 8px; + padding: 2px 8px; + font-size: 12px; + white-space: nowrap; +} + +/* --- Main body --- */ +.pexpo-core-body { + flex: 1 1 auto; + padding: 14px; + overflow:auto; + position: relative; + z-index: 1; +} + +.pexpo-core-metaRow { + display:flex; + align-items:center; + justify-content:space-between; + gap: 10px; + margin-bottom: 12px; + color: var(--muted); + font-size: 12px; +} + +/* Masonry */ +.pexpo-core-masonry { + display:flex; + gap: var(--gap); + align-items:flex-start; +} + +.pexpo-core-mCol { + flex: 1 1 0; + min-width: 0; + display:flex; + flex-direction:column; + gap: var(--gap); +} + +.pexpo-core-card { + border-radius: 14px; + border: 1px solid var(--stroke2); + background: + linear-gradient(180deg, rgba(143, 152, 255, 0.05), rgba(255,255,255,.92)); + padding: 12px; + box-shadow: 0 12px 26px rgba(16,24,40,.10); + display:flex; + flex-direction:column; + gap: 8px; +} + +.pexpo-core-imageWrap { + width: 100%; + height: auto; + border-radius: 10px; + overflow: hidden; +} + +.pexpo-core-imageWrap img { + width: 100%; + height: auto; + display: block; +} + +.pexpo-core-cardTop { + display:flex; + align-items:flex-start; + justify-content:space-between; + gap: 10px; +} + +.pexpo-core-title { + font-weight: 750; + letter-spacing: .15px; +} + +.pexpo-core-tag { + font-size: 12px; + padding: 3px 8px; + border-radius: 999px; + border: 1px solid rgba(149,0,0,.28); + color: rgba(149,0,0,.90); + background: rgba(149,0,0,.06); + white-space:nowrap; +} + +.pexpo-core-desc { + color: rgba(16,24,40,.74); + font-size: 13px; +} + +.pexpo-core-foot { + margin-top:auto; + display:flex; + align-items:center; + justify-content:space-between; + color: rgba(16,24,40,.60); + font-size: 12px; + border-top: 1px dashed rgba(16,24,40,.16); + padding-top: 8px; +} + +.pexpo-core-dot { + width: 7px; height: 7px; + border-radius: 999px; + background: rgba(44,52,137,.55); + display:inline-block; + margin-right: 8px; +} + +.pexpo-core-status { + display:flex; + align-items:center; + gap: 0; +} + +/* Filter drawer */ +.pexpo-core-filterDrawerBackdrop { + position:absolute; + inset: var(--topbarH) 0 0 0; + background: rgba(16,24,40,.20); + backdrop-filter: blur(2px); + opacity: 0; + pointer-events: none; + transition: opacity 180ms ease; + z-index: 2; +} + +.pexpo-core-filterDrawerBackdrop.pexpo-core-show { + opacity: 1; + pointer-events: auto; +} + +.pexpo-core-filterDrawer { + position:absolute; + top: var(--topbarH); + bottom: 0; + left: 14px; + width: var(--filterW); + border-radius: 14px; + border: 1px solid var(--stroke2); + background: rgba(255,255,255,.92); + overflow:hidden; + box-shadow: 0 18px 45px rgba(16,24,40,.18); + opacity: 0; + transform: translateY(-8px); + pointer-events: none; + transition: opacity 200ms ease, transform 200ms ease, width 220ms ease; + z-index: 3; + display:flex; + flex-direction:column; + min-height: 0; + margin: 12.5px 0 12.5px 0; +} + +.pexpo-core-filterDrawer.pexpo-core-open { + opacity: 1; + transform: translateY(0); + pointer-events: auto; + width: var(--filterWOpen); +} + +.pexpo-core-filterPanelHeader { + display:flex; + align-items:center; + justify-content:space-between; + padding: 10px 10px; + border-bottom: 1px solid var(--stroke); + color: rgba(16,24,40,.60); + background: linear-gradient(180deg, rgba(44,52,137,.05), rgba(255,255,255,.0)); + flex: 0 0 auto; +} +.pexpo-core-filterPanelHeader b { color: var(--text); } + +.pexpo-core-jsonBox { + padding: 10px; + flex: 1 1 auto; + overflow:auto; + min-height: 0; +} + +.pexpo-core-textarea { + width: 100%; + height: 210px; + resize: vertical; + min-height: 180px; + max-height: 60vh; + border-radius: 10px; + border: 1px solid rgba(44,52,137,.20); + background: #fbfbfe; + color: rgba(16,24,40,.92); + padding: 10px; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 12px; + line-height: 1.35; + outline: none; +} +.pexpo-core-textarea:focus { + border-color: rgba(44,52,137,.45); + box-shadow: 0 0 0 3px rgba(44,52,137,.14); +} + +.pexpo-core-hint { + margin-top: 8px; + color: rgba(16,24,40,.60); + font-size: 12px; +} + +.pexpo-core-error { + margin-top: 8px; + color: #950000; + font-size: 12px; + display:none; +} +.pexpo-core-error.pexpo-core-show { display:block; } + +/* Drawer open padding */ +.pexpo-core-shell.pexpo-core-drawerOpen .pexpo-core-body { + /* padding-left: calc(14px + var(--filterWOpen) + 14px); */ + transition: padding-left 220ms ease; +} +.pexpo-core-shell:not(.pexpo-core-drawerOpen) .pexpo-core-body { + transition: padding-left 220ms ease; +} + +/* Icons */ + +.pexpo-core-icons-search { + box-sizing: border-box; + position: relative; + display: block; + transform: scale(var(--ggs, 1)); + width: 16px; + height: 16px; + border: 2px solid; + border-radius: 100%; + margin-left: -4px; + margin-top: -4px; +} +.pexpo-core-icons-search:after { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + border-radius: 3px; + width: 3px; + height: 8px; + background: currentColor; + transform: rotate(-45deg); + top: 10px; + left: 12px; +} + +.pexpo-core-icons-down { + box-sizing: border-box; + position: relative; + display: block; + transform: scale(var(--ggs, 1)); + width: 22px; + height: 22px; + border: 2px solid transparent; + border-radius: 100px; +} + +.pexpo-core-icons-down:after { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + width: 10px; + height: 10px; + border-bottom: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + transition: 1s transform ease; + left: 4px; + top: 2px; +} + +.pexpo-core-filterWrap.pexpo-core-open .pexpo-core-icons-down { + transform: rotate(-135deg) translate(-1px,-1px); +} + +.pexpo-core-icons-options { + box-sizing: border-box; + position: relative; + display: block; + transform: scale(var(--ggs, 1)); + width: 10px; + height: 2px; + box-shadow: + -3px 4px 0 0, + 3px -4px 0 0; + margin: 0 10px 0 10px; +} +.pexpo-core-icons-options::after, +.pexpo-core-icons-options::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + width: 8px; + height: 8px; + border: 2px solid; + border-radius: 100%; +} +.pexpo-core-icons-options::before { + top: -7px; + left: -4px; +} +.pexpo-core-icons-options::after { + bottom: -7px; + right: -4px; +} + +@media (max-width: 720px) { + .pexpo-core-topbar { flex-direction:column; } + .pexpo-core-searchWrap { justify-content:stretch; } + .pexpo-core-search { width: 100%; } + .pexpo-core-filterWrap { width: 100%; } + .pexpo-core-filterWrap.pexpo-core-open { width: 100%; } + .pexpo-core-sortWrap { width: 100%; } + .pexpo-core-sortSelect { width: 110px; } + .pexpo-core-searchWrap { width: 100%; } + .pexpo-core-filterDrawer { + left: 14px; + right: 14px; + width: auto; + } + .pexpo-core-filterDrawer.pexpo-core-open { width: auto; } + .pexpo-core-shell.pexpo-core-drawerOpen .pexpo-core-body { padding-left: 14px; } +} \ No newline at end of file diff --git a/public/js/searchbox.js b/public/js/searchbox.js new file mode 100644 index 0000000..ffc36a2 --- /dev/null +++ b/public/js/searchbox.js @@ -0,0 +1,332 @@ +document.addEventListener('DOMContentLoaded', () => { + // ---------- DOM Elements ---------- + const shell = document.getElementById("shell"); + const topbar = document.getElementById("topbar"); + + const filterWrap = document.getElementById("filterWrap"); + const filterBtn = document.getElementById("filterBtn"); + const filterDrawer = document.getElementById("filterDrawer"); + const drawerBackdrop = document.getElementById("drawerBackdrop"); + + const filterJson = document.getElementById("filterJson"); + const jsonError = document.getElementById("jsonError"); + const filterLabel = document.getElementById("filterLabel"); + + const qInput = document.getElementById("q"); + const masonry = document.getElementById("masonry"); + const measure = document.getElementById("measure"); + + const count = document.getElementById("count"); + const layoutMeta = document.getElementById("layoutMeta"); + const sortSelect = document.getElementById("sortSelect"); + + // ---------- State ---------- + let filters = { + sort: "relevance", + status: ["active", "archived"], + tags: ["ui", "mock"], + maxResults: 20 + }; + + let activeSortKey = "relevance"; + let results = []; // Fills via Ajax + + // ---------- Sort Definitions ---------- + const SORTS = { + relevance: { + label: "Relevance", + compare: (a, b) => (b.relevance - a.relevance) || (b.score - a.score) + }, + score_desc: { + label: "Score (high → low)", + compare: (a, b) => (b.score - a.score) + }, + date_desc: { + label: "Date (new → old)", + compare: (a, b) => new Date(b.updated) - new Date(a.updated) + }, + title_asc: { + label: "Title (A → Z)", + compare: (a, b) => (a.title || "").localeCompare(b.title || "") + } + }; + + // ---------- Helpers ---------- + function escapeHtml(str) { + return String(str).replace(/[&<>"']/g, s => ({ + "&": "&", "<": "<", ">": ">", '"': """, "'": "'" + }[s])); + } + + // Generates HTML with the correct 'pexpo-core-' classes + function cardEl(r) { + const div = document.createElement("div"); + div.className = "pexpo-core-card"; + div.innerHTML = ` +
+ ${escapeHtml(r.title)} +
+
+
${escapeHtml(r.title)}
+
+
${escapeHtml(r.desc)}
+
+
+ + score: ${r.score} +
+
${escapeHtml(r.updated)}
+
+ `; + return div; + } + + // ---------- Ajax Function ---------- + async function fetchData(query = "") { + // 1. Show loading state if needed + masonry.style.opacity = "0.5"; + + try { + // REPLACE THIS URL with your actual endpoint + // const response = await fetch(`/api/search?q=${encodeURIComponent(query)}&limit=${filters.maxResults}`); + // const data = await response.json(); + + // --- SIMULATED FETCH FOR DEMO (Remove this block in production) --- + await new Promise(r => setTimeout(r, 600)); // Fake network delay + const data = simulateBackendResponse(query); + // ------------------------------------------------------------------ + + results = data; + + // 2. Compute local relevance if the API doesn't return it + // If your API returns a 'relevance' score, skip this. + computeLocalRelevance(query); + + // 3. Render + requestLayout(); + + } catch (error) { + console.error("Search failed:", error); + masonry.innerHTML = `
Error loading results.
`; + } finally { + masonry.style.opacity = "1"; + } + } + + // Simple client-side scorer if API doesn't rank them + function computeLocalRelevance(query) { + if (!query) { + results.forEach(r => r.relevance = r.score); + return; + } + const q = query.toLowerCase(); + results.forEach(r => { + let hits = 0; + if (r.title.toLowerCase().includes(q)) hits += 50; + if (r.desc.toLowerCase().includes(q)) hits += 20; + r.relevance = hits + r.score; + }); + } + + // ---------- Layout & Masonry Logic ---------- + const minCard = () => Number(getComputedStyle(document.documentElement).getPropertyValue("--cardMin")) || 240; + const gap = () => Number(getComputedStyle(document.documentElement).getPropertyValue("--gap")) || 12; + + let layoutQueued = false; + function requestLayout() { + if (layoutQueued) return; + layoutQueued = true; + requestAnimationFrame(() => { + layoutQueued = false; + applyMasonry(); + }); + } + + function getColumnCount() { + const w = masonry.clientWidth || masonry.getBoundingClientRect().width || 1; + const mc = minCard(); + const g = gap(); + return Math.max(1, Math.floor((w + g) / (mc + g))); + } + + function measureCardHeights(cols, list) { + const w = masonry.clientWidth || 1; + const g = gap(); + const colW = Math.floor((w - (cols - 1) * g) / cols); + measure.style.width = colW + "px"; + measure.innerHTML = ""; + + const measured = list.map((r, idx) => { + const el = cardEl(r); + measure.appendChild(el); + const h = el.offsetHeight; + measure.removeChild(el); + return { r, idx, h }; + }); + + return { measured, colW }; + } + + function packRankAware(measured, cols, K = 6) { + const colHeights = new Array(cols).fill(0); + const placements = []; + const queue = measured.slice(); + + while (queue.length) { + // Find shortest column + let bestCol = 0; + for (let c = 1; c < cols; 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; + } + + 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 = ""; + + const cols = getColumnCount(); + + // Create Columns with pexpo-core class + const colEls = []; + for (let c = 0; c < cols; c++) { + const col = document.createElement("div"); + col.className = "pexpo-core-mCol"; + masonry.appendChild(col); + colEls.push(col); + } + + // Sort + const sorter = SORTS[activeSortKey] || SORTS.relevance; + const ranked = results.slice().sort(sorter.compare); + + // Measure & Pack + const { measured } = measureCardHeights(cols, ranked); + const placements = packRankAware(measured, cols, 6); + + // Render + for (const p of placements) { + colEls[p.col].appendChild(cardEl(p.item.r)); + } + + layoutMeta.textContent = `${cols} cols • ${SORTS[activeSortKey]?.label || "Relevance"}`; + } + + // ---------- UI Events ---------- + + // Sort Dropdown + if (sortSelect) { + sortSelect.innerHTML = Object.entries(SORTS) + .map(([k, def]) => ``) + .join(""); + sortSelect.value = activeSortKey; + sortSelect.addEventListener("change", () => { + activeSortKey = sortSelect.value; + requestLayout(); + }); + } + + // Search + qInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + fetchData(qInput.value.trim()); + } + }); + + // 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(); + if (open) filterJson.focus(); + } + + 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 + fetchData(qInput.value.trim()); + + // 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 + 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 + +
+
+
+ +
+ +
+ + +
+ +
+ +
+
+ + + + +
+
+
0 találat
+
Elrendezés:
+
+ +
+ + +
+
\ No newline at end of file diff --git a/public/search_demo.html b/public/search_demo.html new file mode 100644 index 0000000..c93b006 --- /dev/null +++ b/public/search_demo.html @@ -0,0 +1,1030 @@ + + + + + + Mock Search UI (Masonry) + + + + +
+
+ +
+ +
+ + +
+ + +
+ + +
+ +
+
+ + + + + +
+
+
0 találat
+
Elrendezés:
+
+ + +
+ + + +
+
+ + + + +