From e09ee7da97743bc8bdf2e5d582d9d71c2979749f Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Fri, 5 Jul 2024 15:20:39 +0200 Subject: [PATCH] UI - Visual Selector - Show/visualise all/any matching filter elements from all filters in "CSS/JSONPath/JQ/XPath Filters" include filters (#2440) --- .../res/xpath_element_scraper.js | 73 ++++++++++--------- .../static/js/visual-selector.js | 51 ++++++++----- 2 files changed, 74 insertions(+), 50 deletions(-) diff --git a/changedetectionio/content_fetchers/res/xpath_element_scraper.js b/changedetectionio/content_fetchers/res/xpath_element_scraper.js index 326889ea..ac9ab095 100644 --- a/changedetectionio/content_fetchers/res/xpath_element_scraper.js +++ b/changedetectionio/content_fetchers/res/xpath_element_scraper.js @@ -182,6 +182,7 @@ visibleElementsArray.forEach(function (element) { // Inject the current one set in the include_filters, which may be a CSS rule // used for displaying the current one in VisualSelector, where its not one we generated. if (include_filters.length) { + let results; // Foreach filter, go and find it on the page and add it to the results so we can visualise it again for (const f of include_filters) { bbox = false; @@ -197,10 +198,15 @@ if (include_filters.length) { if (f.startsWith('/') || f.startsWith('xpath')) { var qry_f = f.replace(/xpath(:|\d:)/, '') console.log("[xpath] Scanning for included filter " + qry_f) - q = document.evaluate(qry_f, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; + let xpathResult = document.evaluate(qry_f, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + results = []; + for (let i = 0; i < xpathResult.snapshotLength; i++) { + results.push(xpathResult.snapshotItem(i)); + } } else { console.log("[css] Scanning for included filter " + f) - q = document.querySelector(f); + console.log("[css] Scanning for included filter " + f); + results = document.querySelectorAll(f); } } catch (e) { // Maybe catch DOMException and alert? @@ -208,44 +214,45 @@ if (include_filters.length) { console.log(e); } - if (q) { - // Try to resolve //something/text() back to its /something so we can atleast get the bounding box - try { - if (typeof q.nodeName == 'string' && q.nodeName === '#text') { - q = q.parentElement - } - } catch (e) { - console.log(e) - console.log("xpath_element_scraper: #text resolver") - } + if (results.length) { - // #1231 - IN the case XPath attribute filter is applied, we will have to traverse up and find the element. - if (typeof q.getBoundingClientRect == 'function') { - bbox = q.getBoundingClientRect(); - console.log("xpath_element_scraper: Got filter element, scroll from top was " + scroll_y) - } else { + // Iterate over the results + results.forEach(node => { + // Try to resolve //something/text() back to its /something so we can atleast get the bounding box try { - // Try and see we can find its ownerElement - bbox = q.ownerElement.getBoundingClientRect(); - console.log("xpath_element_scraper: Got filter by ownerElement element, scroll from top was " + scroll_y) + if (typeof node.nodeName == 'string' && node.nodeName === '#text') { + node = node.parentElement + } } catch (e) { console.log(e) - console.log("xpath_element_scraper: error looking up q.ownerElement") + console.log("xpath_element_scraper: #text resolver") } - } - } - if (!q) { - console.log("xpath_element_scraper: filter element " + f + " was not found"); - } + // #1231 - IN the case XPath attribute filter is applied, we will have to traverse up and find the element. + if (typeof node.getBoundingClientRect == 'function') { + bbox = node.getBoundingClientRect(); + console.log("xpath_element_scraper: Got filter element, scroll from top was " + scroll_y) + } else { + try { + // Try and see we can find its ownerElement + bbox = node.ownerElement.getBoundingClientRect(); + console.log("xpath_element_scraper: Got filter by ownerElement element, scroll from top was " + scroll_y) + } catch (e) { + console.log(e) + console.log("xpath_element_scraper: error looking up q.ownerElement") + } + } - if (bbox && bbox['width'] > 0 && bbox['height'] > 0) { - size_pos.push({ - xpath: f, - width: parseInt(bbox['width']), - height: parseInt(bbox['height']), - left: parseInt(bbox['left']), - top: parseInt(bbox['top']) + scroll_y + if (bbox && bbox['width'] > 0 && bbox['height'] > 0) { + size_pos.push({ + xpath: f, + width: parseInt(bbox['width']), + height: parseInt(bbox['height']), + left: parseInt(bbox['left']), + top: parseInt(bbox['top']) + scroll_y, + highlight_as_custom_filter: true + }); + } }); } } diff --git a/changedetectionio/static/js/visual-selector.js b/changedetectionio/static/js/visual-selector.js index 9432ae9f..24d1ee18 100644 --- a/changedetectionio/static/js/visual-selector.js +++ b/changedetectionio/static/js/visual-selector.js @@ -28,32 +28,34 @@ $(document).ready(function () { bootstrap_visualselector(); }); + function clear_reset() { + state_clicked = false; + ctx.clearRect(0, 0, c.width, c.height); + if($("#include_filters").val().length) { + alert("Existing filters under the 'Filters & Triggers' tab were cleared."); + } + $("#include_filters").val(''); + } + $(document).on('keydown', function (event) { if ($("img#selector-background").is(":visible")) { if (event.key == "Escape") { - state_clicked = false; - ctx.clearRect(0, 0, c.width, c.height); + clear_reset(); } } }); + // Handle clearing button/link + $('#clear-selector').on('click', function (event) { + clear_reset(); + }); + // For when the page loads if (!window.location.hash || window.location.hash != '#visualselector') { $("img#selector-background").attr('src', ''); return; } - // Handle clearing button/link - $('#clear-selector').on('click', function (event) { - if (!state_clicked) { - alert('Oops, Nothing selected!'); - } - state_clicked = false; - ctx.clearRect(0, 0, c.width, c.height); - xctx.clearRect(0, 0, c.width, c.height); - $("#include_filters").val(''); - }); - bootstrap_visualselector(); @@ -117,12 +119,13 @@ $(document).ready(function () { selector_image = $("img#selector-background")[0]; selector_image_rect = selector_image.getBoundingClientRect(); - // make the canvas the same size as the image - $('#selector-canvas').attr('height', selector_image_rect.height); - $('#selector-canvas').attr('width', selector_image_rect.width); + // Make the overlayed canvas the same size as the image + $('#selector-canvas').attr('height', selector_image_rect.height).attr('width', selector_image_rect.width); $('#selector-wrapper').attr('width', selector_image_rect.width); - x_scale = selector_image_rect.width / selector_data['browser_width']; + + x_scale = selector_image_rect.width / selector_image.naturalWidth; y_scale = selector_image_rect.height / selector_image.naturalHeight; + ctx.strokeStyle = 'rgba(255,0,0, 0.9)'; ctx.fillStyle = 'rgba(255,0,0, 0.1)'; ctx.lineWidth = 3; @@ -135,6 +138,7 @@ $(document).ready(function () { set_scale(); highlight_current_selected_i(); }); + var selector_currnt_xpath_text = $("#selector-current-xpath span"); set_scale(); @@ -164,6 +168,7 @@ $(document).ready(function () { if (!found) { alert("Unfortunately your existing CSS/xPath Filter was no longer found!"); } + highlight_matching_filters(); } @@ -243,6 +248,18 @@ $(document).ready(function () { } + function highlight_matching_filters() { + selector_data['size_pos'].forEach(sel => { + if (sel.highlight_as_custom_filter) { + xctx.fillStyle = 'rgba(205,205,205,0.95)'; + xctx.strokeStyle = 'rgba(225,0,0,0.95)'; + xctx.lineWidth = 1; + xctx.clearRect(sel.left * x_scale, sel.top * y_scale, sel.width * x_scale, sel.height * y_scale); + xctx.strokeRect(sel.left * x_scale, sel.top * y_scale, sel.width * x_scale, sel.height * y_scale); + } + }); + } + $('#selector-canvas').bind('mousedown', function (e) { highlight_current_selected_i(); });