diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 4ba09989f08..656f07688c0 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -1430,6 +1430,27 @@ def form_share_put_watch(): # paste in etc return redirect(url_for('index')) + @app.route("/highlight_submit_ignore_url", methods=['POST']) + def highlight_submit_ignore_url(): + import re + mode = request.form.get('mode') + selection = request.form.get('selection') + + uuid = request.args.get('uuid','') + if datastore.data["watching"].get(uuid): + if mode == 'exact': + for l in selection.splitlines(): + datastore.data["watching"][uuid]['ignore_text'].append(l.strip()) + elif mode == 'digit-regex': + for l in selection.splitlines(): + # Replace any series of numbers with a regex + s = re.escape(l.strip()) + s = re.sub(r'[0-9]+', r'\\d+', s) + datastore.data["watching"][uuid]['ignore_text'].append('/' + s + '/') + + return f"Click to preview" + + import changedetectionio.blueprint.browser_steps as browser_steps app.register_blueprint(browser_steps.construct_blueprint(datastore), url_prefix='/browser-steps') diff --git a/changedetectionio/static/js/diff-overview.js b/changedetectionio/static/js/diff-overview.js index 31c9bfddfe1..367c8472480 100644 --- a/changedetectionio/static/js/diff-overview.js +++ b/changedetectionio/static/js/diff-overview.js @@ -1,4 +1,13 @@ $(document).ready(function () { + var csrftoken = $('input[name=csrf_token]').val(); + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken) + } + } + }) + // Load it when the #screenshot tab is in use, so we dont give a slow experience when waiting for the text diff to load window.addEventListener('hashchange', function (e) { toggle(location.hash); @@ -15,11 +24,71 @@ $(document).ready(function () { $("#settings").hide(); } else if (hash_name === '#extract') { $("#settings").hide(); + } else { + $("#settings").show(); } + } + const article = $('.highlightable-filter')[0]; + + // We could also add the 'touchend' event for touch devices, but since + // most iOS/Android browsers already show a dialog when you select + // text (often with a Share option) we'll skip that + article.addEventListener('mouseup', dragTextHandler, false); + article.addEventListener('mousedown', clean, false); + + function clean(event) { + $("#highlightSnippet").remove(); + } + + + function dragTextHandler(event) { + console.log('mouseupped'); + + // Check if any text was selected + if (window.getSelection().toString().length > 0) { + + // Find out how much (if any) user has scrolled + var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop; + + // Get cursor position + const posX = event.clientX; + const posY = event.clientY + 20 + scrollTop; + + // Append HTML to the body, create the "Tweet Selection" dialog + document.body.insertAdjacentHTML('beforeend', '
Ignore any change on any line which contains the selected text.

Ignore exact text 
'); + + if (/\d/.test(window.getSelection().toString())) { + // Offer regex replacement + document.getElementById("highlightSnippet").insertAdjacentHTML('beforeend', 'Ignore text including number changes'); + } + + $('#highlightSnippet a').bind('click', function (e) { + if(!window.getSelection().toString().trim().length) { + alert('Oops no text selected!'); + return; + } + + $.ajax({ + type: "POST", + url: highlight_submit_ignore_url, + data: {'mode': $(this).data('mode'), 'selection': window.getSelection().toString()}, + statusCode: { + 400: function () { + // More than likely the CSRF token was lost when the server restarted + alert("There was a problem processing the request, please reload the page."); + } + } + }).done(function (data) { + $("#highlightSnippet").append(data) + }).fail(function (data) { + console.log(data); + alert('There was an error communicating with the server.'); + }); + }); - else { - $("#settings").show(); } } + + }); diff --git a/changedetectionio/static/styles/diff.css b/changedetectionio/static/styles/diff.css index a9d245af5ad..48fc3e731d2 100644 --- a/changedetectionio/static/styles/diff.css +++ b/changedetectionio/static/styles/diff.css @@ -218,3 +218,10 @@ td#diff-col div { text-align: center; } .tab-pane-inner#screenshot img { max-width: 99%; } + +#highlightSnippet { + background: var(--color-background); + padding: 1em; + border-radius: 5px; + background: var(--color-background); + box-shadow: 1px 1px 4px var(--color-shadow-jump); } diff --git a/changedetectionio/static/styles/scss/diff.scss b/changedetectionio/static/styles/scss/diff.scss index 19783b6fa41..699f24ebdb4 100644 --- a/changedetectionio/static/styles/scss/diff.scss +++ b/changedetectionio/static/styles/scss/diff.scss @@ -119,3 +119,11 @@ td#diff-col div { max-width: 99%; } } + +#highlightSnippet { + background: var(--color-background); + padding: 1em; + border-radius: 5px; + background: var(--color-background); + box-shadow: 1px 1px 4px var(--color-shadow-jump); +} diff --git a/changedetectionio/templates/diff.html b/changedetectionio/templates/diff.html index 22e34901d64..4dbb67a1487 100644 --- a/changedetectionio/templates/diff.html +++ b/changedetectionio/templates/diff.html @@ -6,6 +6,9 @@ {% if last_error_screenshot %} const error_screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid, error_screenshot=1) }}"; {% endif %} + + const highlight_submit_ignore_url="{{url_for('highlight_submit_ignore_url', uuid=uuid)}}"; + @@ -76,7 +79,7 @@

Differences

-
Pro-tip: Use show current snapshot tab to visualise what will be ignored.
+
Pro-tip: Use show current snapshot tab to visualise what will be ignored, highlight text to add to ignore filters
{% if password_enabled_and_share_is_off %}
Pro-tip: You can enable "share access when password is enabled" from settings
@@ -91,7 +94,7 @@

Differences

{{previous}} {{newest}} - + diff --git a/changedetectionio/templates/preview.html b/changedetectionio/templates/preview.html index ead4e3b4102..5cc61bedc4a 100644 --- a/changedetectionio/templates/preview.html +++ b/changedetectionio/templates/preview.html @@ -6,6 +6,7 @@ {% if last_error_screenshot %} const error_screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid, error_screenshot=1) }}"; {% endif %} + const highlight_submit_ignore_url="{{url_for('highlight_submit_ignore_url', uuid=uuid)}}"; @@ -20,7 +21,7 @@ {% endif %}
- +
{{watch.error_text_ctime|format_seconds_ago}} seconds ago
@@ -36,11 +37,12 @@
{{watch.snapshot_text_ctime|format_timestamp_timeago}}
- Grey lines are ignored Blue lines are triggers + Grey lines are ignored Blue lines are triggers Pro-tip: Highlight text to add to ignore filters + -
+ {% for row in content %}
{{row.line}}
{% endfor %} diff --git a/changedetectionio/tests/test_ignorehighlighter.py b/changedetectionio/tests/test_ignorehighlighter.py new file mode 100644 index 00000000000..88bd0af61db --- /dev/null +++ b/changedetectionio/tests/test_ignorehighlighter.py @@ -0,0 +1,57 @@ +#!/usr/bin/python3 + +import time +from flask import url_for +from .util import live_server_setup, wait_for_all_checks +from changedetectionio import html_tools +from . util import extract_UUID_from_client + +def set_original_ignore_response(): + test_return_data = """ + + Some initial text
+

Which is across multiple lines

+
+ So let's see what happens.
+

oh yeah 456

+ + + + """ + + with open("test-datastore/endpoint-content.txt", "w") as f: + f.write(test_return_data) + + +def test_highlight_ignore(client, live_server): + live_server_setup(live_server) + set_original_ignore_response() + test_url = url_for('test_endpoint', _external=True) + res = client.post( + url_for("import_page"), + data={"urls": test_url}, + follow_redirects=True + ) + assert b"1 Imported" in res.data + + # Give the thread time to pick it up + wait_for_all_checks(client) + uuid = extract_UUID_from_client(client) + # use the highlighter endpoint + res = client.post( + url_for("highlight_submit_ignore_url", uuid=uuid), + data={"mode": 'digit-regex', 'selection': 'oh yeah 123'}, + follow_redirects=True + ) + + res = client.get(url_for("edit_page", uuid=uuid)) + + # should be a regex now + assert b'/oh\ yeah\ \d+/' in res.data + + # Should return a link + assert b'href' in res.data + + # And it should register in the preview page + res = client.get(url_for("preview_page", uuid=uuid)) + assert b'
oh yeah 456' in res.data