From 38d9fb54158e45d126ecab318571421388630edd Mon Sep 17 00:00:00 2001 From: Nika Siradze Date: Wed, 1 Jul 2026 10:34:43 +0400 Subject: [PATCH] Scrape UX: auto-detect first, visual item cards, selectors behind Edit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'Detect content' is now the primary action: Auto-detect fields runs the AI detection then immediately previews, so you see what will be scraped. - Preview renders visual cards (image thumb, title, labelled fields) instead of a text line. - The CSS item-selector / fields / next-page rows are tucked behind an 'Edit selectors' toggle (hidden by default) — minimal manual work unless you want it. --- assets/css/admin.css | 45 ++++++++++++++++++ src/Admin/views/sources.php | 93 ++++++++++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/assets/css/admin.css b/assets/css/admin.css index 756e342..3ee6b07 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -327,6 +327,51 @@ max-width: 880px; } +.aggregate-it .ai-scrape-cards { + display: grid; + grid-template-columns: repeat( auto-fill, minmax( 240px, 1fr ) ); + gap: 12px; +} + +.aggregate-it .ai-scrape-card { + display: flex; + gap: 10px; + border: 1px solid #c3c4c7; + border-radius: 4px; + padding: 10px; + background: #fff; +} + +.aggregate-it .ai-scrape-card img { + width: 56px; + height: 56px; + object-fit: cover; + border-radius: 4px; + flex: 0 0 auto; +} + +.aggregate-it .ai-scrape-card-body { + min-width: 0; + font-size: 12px; +} + +.aggregate-it .ai-scrape-card-body strong { + display: block; + margin-bottom: 4px; + font-size: 13px; +} + +.aggregate-it .ai-scrape-line { + color: #50575e; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aggregate-it .ai-scrape-key { + color: #8c8f94; +} + .aggregate-it .ai-preview td { font-size: 12px; } diff --git a/src/Admin/views/sources.php b/src/Admin/views/sources.php index ec07904..ed8f66c 100644 --- a/src/Admin/views/sources.php +++ b/src/Admin/views/sources.php @@ -175,13 +175,11 @@ - + - - @@ -199,7 +197,7 @@

- + @@ -232,7 +230,7 @@
- + - + - +

+ + + +

+
@@ -378,6 +381,7 @@ var type = document.getElementById( 'ai-source-type' ); var mode = document.getElementById( 'ai-scrape-mode' ); if ( ! type ) { return; } + var advOn = false; function show( selector, on ) { document.querySelectorAll( selector ).forEach( function ( el ) { el.style.display = on ? '' : 'none'; } ); } @@ -388,9 +392,19 @@ function apply() { var sitemap = mode && mode.value === 'sitemap'; show( '.ai-scrape-list', scrape && ! sitemap ); show( '.ai-scrape-sitemap', scrape && sitemap ); + // Selectors are auto-filled by detection; keep them tucked away unless asked for. + if ( scrape && ! advOn ) { show( '.ai-scrape-adv', false ); } } type.addEventListener( 'change', apply ); if ( mode ) { mode.addEventListener( 'change', apply ); } + var editSel = document.getElementById( 'ai-edit-selectors' ); + if ( editSel ) { + editSel.addEventListener( 'click', function () { + advOn = ! advOn; + editSel.textContent = advOn ? 'Hide selectors' : 'Edit selectors'; + apply(); + } ); + } apply(); var suggest = document.getElementById( 'ai-suggest' ); @@ -414,7 +428,9 @@ function apply() { return; } fill( data.suggestion || {} ); - if ( status ) { status.textContent = 'Filled — review and save.'; } + if ( status ) { status.textContent = 'Detected — showing a sample of what will be scraped…'; } + var pv = document.getElementById( 'ai-preview-btn' ); + if ( pv ) { pv.click(); } } ).catch( function () { suggest.disabled = false; if ( status ) { status.textContent = 'Could not suggest fields.'; } @@ -476,23 +492,56 @@ function robots() { function renderPreview( box, rows ) { if ( ! box ) { return; } box.innerHTML = ''; - if ( ! rows.length ) { return; } - var table = document.createElement( 'table' ); - table.className = 'widefat striped'; + if ( ! rows.length ) { + box.textContent = 'No items matched — try Edit selectors below.'; + return; + } + var grid = document.createElement( 'div' ); + grid.className = 'ai-scrape-cards'; + rows.forEach( function ( r ) { + var card = document.createElement( 'div' ); + card.className = 'ai-scrape-card'; + + if ( r.image ) { + var img = document.createElement( 'img' ); + img.src = r.image; + img.loading = 'lazy'; + img.alt = ''; + card.appendChild( img ); + } + + var body = document.createElement( 'div' ); + body.className = 'ai-scrape-card-body'; + + if ( r.title ) { + var h = document.createElement( 'strong' ); + h.textContent = r.title; + body.appendChild( h ); + } + + function addLine( label, val ) { + if ( ! val ) { return; } + var line = document.createElement( 'div' ); + line.className = 'ai-scrape-line'; + var key = document.createElement( 'span' ); + key.className = 'ai-scrape-key'; + key.textContent = label + ': '; + line.appendChild( key ); + line.appendChild( document.createTextNode( String( val ) ) ); + body.appendChild( line ); + } + + addLine( 'date', r.date ); + addLine( 'url', r.url ); var fields = r.fields || {}; - var parts = []; - if ( r.title ) { parts.push( r.title ); } - if ( r.url ) { parts.push( r.url ); } - if ( r.date ) { parts.push( String( r.date ) ); } - Object.keys( fields ).forEach( function ( k ) { parts.push( k + ': ' + fields[ k ] ); } ); - var tr = document.createElement( 'tr' ); - var td = document.createElement( 'td' ); - td.textContent = parts.join( ' • ' ) || '(empty)'; - tr.appendChild( td ); - table.appendChild( tr ); + Object.keys( fields ).forEach( function ( k ) { addLine( k, fields[ k ] ); } ); + + card.appendChild( body ); + grid.appendChild( card ); } ); - box.appendChild( table ); + + box.appendChild( grid ); } function fill( s ) {