mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-23 18:06:09 +00:00
Compare commits
1 Commits
add-button
...
ui-mobile-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c7f6ac182 |
@@ -4,9 +4,7 @@ from loguru import logger
|
|||||||
from changedetectionio.content_fetchers.exceptions import BrowserStepsStepException
|
from changedetectionio.content_fetchers.exceptions import BrowserStepsStepException
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Visual Selector scraper - 'Button' is there because some sites have <button>OUT OF STOCK</button>.
|
visualselector_xpath_selectors = 'div,span,form,table,tbody,tr,td,a,p,ul,li,h1,h2,h3,h4,header,footer,section,article,aside,details,main,nav,section,summary'
|
||||||
visualselector_xpath_selectors = 'div,span,form,table,tbody,tr,td,a,p,ul,li,h1,h2,h3,h4,header,footer,section,article,aside,details,main,nav,section,summary,button'
|
|
||||||
|
|
||||||
|
|
||||||
# available_fetchers() will scan this implementation looking for anything starting with html_
|
# available_fetchers() will scan this implementation looking for anything starting with html_
|
||||||
# this information is used in the form selections
|
# this information is used in the form selections
|
||||||
|
|||||||
@@ -1158,6 +1158,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
@login_optionally_required
|
@login_optionally_required
|
||||||
def preview_page(uuid):
|
def preview_page(uuid):
|
||||||
content = []
|
content = []
|
||||||
|
ignored_line_numbers = []
|
||||||
|
trigger_line_numbers = []
|
||||||
versions = []
|
versions = []
|
||||||
timestamp = None
|
timestamp = None
|
||||||
|
|
||||||
@@ -1174,10 +1176,11 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver'
|
system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver'
|
||||||
extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')]
|
extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')]
|
||||||
|
|
||||||
|
|
||||||
is_html_webdriver = False
|
is_html_webdriver = False
|
||||||
if (watch.get('fetch_backend') == 'system' and system_uses_webdriver) or watch.get('fetch_backend') == 'html_webdriver' or watch.get('fetch_backend', '').startswith('extra_browser_'):
|
if (watch.get('fetch_backend') == 'system' and system_uses_webdriver) or watch.get('fetch_backend') == 'html_webdriver' or watch.get('fetch_backend', '').startswith('extra_browser_'):
|
||||||
is_html_webdriver = True
|
is_html_webdriver = True
|
||||||
triggered_line_numbers = []
|
|
||||||
if datastore.data['watching'][uuid].history_n == 0 and (watch.get_error_text() or watch.get_error_snapshot()):
|
if datastore.data['watching'][uuid].history_n == 0 and (watch.get_error_text() or watch.get_error_snapshot()):
|
||||||
flash("Preview unavailable - No fetch/check completed or triggers not reached", "error")
|
flash("Preview unavailable - No fetch/check completed or triggers not reached", "error")
|
||||||
else:
|
else:
|
||||||
@@ -1190,12 +1193,31 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
versions = list(watch.history.keys())
|
versions = list(watch.history.keys())
|
||||||
content = watch.get_history_snapshot(timestamp)
|
tmp = watch.get_history_snapshot(timestamp).splitlines()
|
||||||
|
|
||||||
triggered_line_numbers = html_tools.strip_ignore_text(content=content,
|
# Get what needs to be highlighted
|
||||||
|
ignore_rules = watch.get('ignore_text', []) + datastore.data['settings']['application']['global_ignore_text']
|
||||||
|
|
||||||
|
# .readlines will keep the \n, but we will parse it here again, in the future tidy this up
|
||||||
|
ignored_line_numbers = html_tools.strip_ignore_text(content="\n".join(tmp),
|
||||||
|
wordlist=ignore_rules,
|
||||||
|
mode='line numbers'
|
||||||
|
)
|
||||||
|
|
||||||
|
trigger_line_numbers = html_tools.strip_ignore_text(content="\n".join(tmp),
|
||||||
wordlist=watch['trigger_text'],
|
wordlist=watch['trigger_text'],
|
||||||
mode='line numbers'
|
mode='line numbers'
|
||||||
)
|
)
|
||||||
|
# Prepare the classes and lines used in the template
|
||||||
|
i=0
|
||||||
|
for l in tmp:
|
||||||
|
classes=[]
|
||||||
|
i+=1
|
||||||
|
if i in ignored_line_numbers:
|
||||||
|
classes.append('ignored')
|
||||||
|
if i in trigger_line_numbers:
|
||||||
|
classes.append('triggered')
|
||||||
|
content.append({'line': l, 'classes': ' '.join(classes)})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
content.append({'line': f"File doesnt exist or unable to read timestamp {timestamp}", 'classes': ''})
|
content.append({'line': f"File doesnt exist or unable to read timestamp {timestamp}", 'classes': ''})
|
||||||
@@ -1206,7 +1228,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
history_n=watch.history_n,
|
history_n=watch.history_n,
|
||||||
extra_stylesheets=extra_stylesheets,
|
extra_stylesheets=extra_stylesheets,
|
||||||
extra_title=f" - Diff - {watch.label} @ {timestamp}",
|
extra_title=f" - Diff - {watch.label} @ {timestamp}",
|
||||||
triggered_line_numbers=triggered_line_numbers,
|
ignored_line_numbers=ignored_line_numbers,
|
||||||
|
triggered_line_numbers=trigger_line_numbers,
|
||||||
current_diff_url=watch['url'],
|
current_diff_url=watch['url'],
|
||||||
screenshot=watch.get_screenshot(),
|
screenshot=watch.get_screenshot(),
|
||||||
watch=watch,
|
watch=watch,
|
||||||
@@ -1377,11 +1400,9 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
# Return a 500 error
|
# Return a 500 error
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
# Ajax callback
|
|
||||||
@app.route("/edit/<string:uuid>/preview-rendered", methods=['POST'])
|
@app.route("/edit/<string:uuid>/preview-rendered", methods=['POST'])
|
||||||
@login_optionally_required
|
@login_optionally_required
|
||||||
def watch_get_preview_rendered(uuid):
|
def watch_get_preview_rendered(uuid):
|
||||||
from flask import jsonify
|
|
||||||
'''For when viewing the "preview" of the rendered text from inside of Edit'''
|
'''For when viewing the "preview" of the rendered text from inside of Edit'''
|
||||||
now = time.time()
|
now = time.time()
|
||||||
import brotli
|
import brotli
|
||||||
@@ -1413,7 +1434,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
update_handler.fetcher.content = decompressed_data
|
update_handler.fetcher.content = decompressed_data
|
||||||
update_handler.fetcher.headers['content-type'] = tmp_watch.get('content-type')
|
update_handler.fetcher.headers['content-type'] = tmp_watch.get('content-type')
|
||||||
try:
|
try:
|
||||||
changed_detected, update_obj, text_after_filter = update_handler.run_changedetection(
|
changed_detected, update_obj, contents, text_after_filter = update_handler.run_changedetection(
|
||||||
watch=tmp_watch,
|
watch=tmp_watch,
|
||||||
skip_when_checksum_same=False,
|
skip_when_checksum_same=False,
|
||||||
)
|
)
|
||||||
@@ -1427,32 +1448,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
if not text_after_filter.strip():
|
if not text_after_filter.strip():
|
||||||
text_after_filter = 'Empty content'
|
text_after_filter = 'Empty content'
|
||||||
|
|
||||||
# because run_changedetection always returns bytes due to saving the snapshots etc
|
logger.trace(f"Parsed in {time.time()-now:.3f}s")
|
||||||
text_after_filter = text_after_filter.decode('utf-8') if isinstance(text_after_filter, bytes) else text_after_filter
|
return text_after_filter.strip()
|
||||||
|
|
||||||
do_anchor = datastore.data["settings"]["application"].get("render_anchor_tag_content", False)
|
|
||||||
|
|
||||||
trigger_line_numbers = []
|
|
||||||
try:
|
|
||||||
text_before_filter = html_tools.html_to_text(html_content=decompressed_data,
|
|
||||||
render_anchor_tag_content=do_anchor)
|
|
||||||
|
|
||||||
trigger_line_numbers = html_tools.strip_ignore_text(content=text_after_filter,
|
|
||||||
wordlist=tmp_watch['trigger_text'],
|
|
||||||
mode='line numbers'
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
text_before_filter = f"Error: {str(e)}"
|
|
||||||
|
|
||||||
logger.trace(f"Parsed in {time.time() - now:.3f}s")
|
|
||||||
|
|
||||||
return jsonify(
|
|
||||||
{
|
|
||||||
'after_filter': text_after_filter,
|
|
||||||
'before_filter': text_before_filter.decode('utf-8') if isinstance(text_before_filter, bytes) else text_before_filter,
|
|
||||||
'trigger_line_numbers': trigger_line_numbers
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/form/add/quickwatch", methods=['POST'])
|
@app.route("/form/add/quickwatch", methods=['POST'])
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from changedetectionio.strtobool import strtobool
|
||||||
|
|
||||||
@@ -476,7 +475,7 @@ class processor_text_json_diff_form(commonSettingsForm):
|
|||||||
|
|
||||||
title = StringField('Title', default='')
|
title = StringField('Title', default='')
|
||||||
|
|
||||||
ignore_text = StringListField('Remove lines containing', [ValidateListRegex()])
|
ignore_text = StringListField('Ignore text', [ValidateListRegex()])
|
||||||
headers = StringDictKeyValue('Request headers')
|
headers = StringDictKeyValue('Request headers')
|
||||||
body = TextAreaField('Request body', [validators.Optional()])
|
body = TextAreaField('Request body', [validators.Optional()])
|
||||||
method = SelectField('Request method', choices=valid_method, default=default_method)
|
method = SelectField('Request method', choices=valid_method, default=default_method)
|
||||||
@@ -526,16 +525,9 @@ class processor_text_json_diff_form(commonSettingsForm):
|
|||||||
try:
|
try:
|
||||||
from changedetectionio.safe_jinja import render as jinja_render
|
from changedetectionio.safe_jinja import render as jinja_render
|
||||||
jinja_render(template_str=self.url.data)
|
jinja_render(template_str=self.url.data)
|
||||||
except ModuleNotFoundError as e:
|
|
||||||
# incase jinja2_time or others is missing
|
|
||||||
logger.error(e)
|
|
||||||
self.url.errors.append(e)
|
|
||||||
result = False
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
|
||||||
self.url.errors.append('Invalid template syntax')
|
self.url.errors.append('Invalid template syntax')
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
class SingleExtraProxy(Form):
|
class SingleExtraProxy(Form):
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
|
||||||
from changedetectionio.content_fetchers.base import Fetcher
|
from changedetectionio.content_fetchers.base import Fetcher
|
||||||
from changedetectionio.strtobool import strtobool
|
from changedetectionio.strtobool import strtobool
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
import hashlib
|
import hashlib
|
||||||
import importlib
|
|
||||||
import inspect
|
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
|
||||||
import re
|
import re
|
||||||
|
import importlib
|
||||||
|
import pkgutil
|
||||||
|
import inspect
|
||||||
|
|
||||||
class difference_detection_processor():
|
class difference_detection_processor():
|
||||||
|
|
||||||
@@ -155,12 +157,12 @@ class difference_detection_processor():
|
|||||||
# After init, call run_changedetection() which will do the actual change-detection
|
# After init, call run_changedetection() which will do the actual change-detection
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_changedetection(self, watch, skip_when_checksum_same: bool = True):
|
def run_changedetection(self, watch, skip_when_checksum_same=True):
|
||||||
update_obj = {'last_notification_error': False, 'last_error': False}
|
update_obj = {'last_notification_error': False, 'last_error': False}
|
||||||
some_data = 'xxxxx'
|
some_data = 'xxxxx'
|
||||||
update_obj["previous_md5"] = hashlib.md5(some_data.encode('utf-8')).hexdigest()
|
update_obj["previous_md5"] = hashlib.md5(some_data.encode('utf-8')).hexdigest()
|
||||||
changed_detected = False
|
changed_detected = False
|
||||||
return changed_detected, update_obj, ''.encode('utf-8')
|
return changed_detected, update_obj, ''.encode('utf-8'), b''
|
||||||
|
|
||||||
|
|
||||||
def find_sub_packages(package_name):
|
def find_sub_packages(package_name):
|
||||||
|
|||||||
@@ -229,13 +229,12 @@ class perform_site_check(difference_detection_processor):
|
|||||||
xpath_data=self.fetcher.xpath_data
|
xpath_data=self.fetcher.xpath_data
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"self.fetcher.instock_data is - '{self.fetcher.instock_data}' and itemprop_availability.get('availability') is {itemprop_availability.get('availability')}")
|
|
||||||
# Nothing automatic in microdata found, revert to scraping the page
|
# Nothing automatic in microdata found, revert to scraping the page
|
||||||
if self.fetcher.instock_data and itemprop_availability.get('availability') is None:
|
if self.fetcher.instock_data and itemprop_availability.get('availability') is None:
|
||||||
# 'Possibly in stock' comes from stock-not-in-stock.js when no string found above the fold.
|
# 'Possibly in stock' comes from stock-not-in-stock.js when no string found above the fold.
|
||||||
# Careful! this does not really come from chrome/js when the watch is set to plaintext
|
# Careful! this does not really come from chrome/js when the watch is set to plaintext
|
||||||
update_obj['restock']["in_stock"] = True if self.fetcher.instock_data == 'Possibly in stock' else False
|
update_obj['restock']["in_stock"] = True if self.fetcher.instock_data == 'Possibly in stock' else False
|
||||||
logger.debug(f"Watch UUID {watch.get('uuid')} restock check returned instock_data - '{self.fetcher.instock_data}' from JS scraper.")
|
logger.debug(f"Watch UUID {watch.get('uuid')} restock check returned '{self.fetcher.instock_data}' from JS scraper.")
|
||||||
|
|
||||||
# What we store in the snapshot
|
# What we store in the snapshot
|
||||||
price = update_obj.get('restock').get('price') if update_obj.get('restock').get('price') else ""
|
price = update_obj.get('restock').get('price') if update_obj.get('restock').get('price') else ""
|
||||||
@@ -299,4 +298,4 @@ class perform_site_check(difference_detection_processor):
|
|||||||
# Always record the new checksum
|
# Always record the new checksum
|
||||||
update_obj["previous_md5"] = fetched_md5
|
update_obj["previous_md5"] = fetched_md5
|
||||||
|
|
||||||
return changed_detected, update_obj, snapshot_content.encode('utf-8').strip()
|
return changed_detected, update_obj, snapshot_content.encode('utf-8').strip(), b''
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ class perform_site_check(difference_detection_processor):
|
|||||||
render_anchor_tag_content=do_anchor,
|
render_anchor_tag_content=do_anchor,
|
||||||
is_rss=is_rss) # 1874 activate the <title workaround hack
|
is_rss=is_rss) # 1874 activate the <title workaround hack
|
||||||
|
|
||||||
|
|
||||||
if watch.get('trim_text_whitespace'):
|
if watch.get('trim_text_whitespace'):
|
||||||
stripped_text_from_html = '\n'.join(line.strip() for line in stripped_text_from_html.replace("\n\n", "\n").splitlines())
|
stripped_text_from_html = '\n'.join(line.strip() for line in stripped_text_from_html.replace("\n\n", "\n").splitlines())
|
||||||
|
|
||||||
@@ -214,8 +215,8 @@ class perform_site_check(difference_detection_processor):
|
|||||||
stripped_text_from_html = stripped_text_from_html.replace("\n\n", "\n")
|
stripped_text_from_html = stripped_text_from_html.replace("\n\n", "\n")
|
||||||
stripped_text_from_html = '\n'.join(sorted(stripped_text_from_html.splitlines(), key=lambda x: x.lower()))
|
stripped_text_from_html = '\n'.join(sorted(stripped_text_from_html.splitlines(), key=lambda x: x.lower()))
|
||||||
|
|
||||||
|
|
||||||
# Re #340 - return the content before the 'ignore text' was applied
|
# Re #340 - return the content before the 'ignore text' was applied
|
||||||
# Also used to calculate/show what was removed
|
|
||||||
text_content_before_ignored_filter = stripped_text_from_html.encode('utf-8')
|
text_content_before_ignored_filter = stripped_text_from_html.encode('utf-8')
|
||||||
|
|
||||||
# @todo whitespace coming from missing rtrim()?
|
# @todo whitespace coming from missing rtrim()?
|
||||||
@@ -240,8 +241,8 @@ class perform_site_check(difference_detection_processor):
|
|||||||
if not rendered_diff and stripped_text_from_html:
|
if not rendered_diff and stripped_text_from_html:
|
||||||
# We had some content, but no differences were found
|
# We had some content, but no differences were found
|
||||||
# Store our new file as the MD5 so it will trigger in the future
|
# Store our new file as the MD5 so it will trigger in the future
|
||||||
c = hashlib.md5(stripped_text_from_html.encode('utf-8').translate(None, b'\r\n\t ')).hexdigest()
|
c = hashlib.md5(text_content_before_ignored_filter.translate(None, b'\r\n\t ')).hexdigest()
|
||||||
return False, {'previous_md5': c}, stripped_text_from_html.encode('utf-8')
|
return False, {'previous_md5': c}, stripped_text_from_html.encode('utf-8'), stripped_text_from_html.encode('utf-8')
|
||||||
else:
|
else:
|
||||||
stripped_text_from_html = rendered_diff
|
stripped_text_from_html = rendered_diff
|
||||||
|
|
||||||
@@ -364,5 +365,4 @@ class perform_site_check(difference_detection_processor):
|
|||||||
if not watch.get('previous_md5'):
|
if not watch.get('previous_md5'):
|
||||||
watch['previous_md5'] = fetched_md5
|
watch['previous_md5'] = fetched_md5
|
||||||
|
|
||||||
# stripped_text_from_html - Everything after filters and NO 'ignored' content
|
return changed_detected, update_obj, text_content_before_ignored_filter, stripped_text_from_html
|
||||||
return changed_detected, update_obj, stripped_text_from_html
|
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
(function($) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
$('#code-block').highlightLines([
|
|
||||||
{
|
|
||||||
'color': '#dd0000',
|
|
||||||
'lines': [10, 12]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'color': '#ee0000',
|
|
||||||
'lines': [15, 18]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
$.fn.highlightLines = function(configurations) {
|
|
||||||
return this.each(function() {
|
|
||||||
const $pre = $(this);
|
|
||||||
const textContent = $pre.text();
|
|
||||||
const lines = textContent.split(/\r?\n/); // Handles both \n and \r\n line endings
|
|
||||||
|
|
||||||
// Build a map of line numbers to styles
|
|
||||||
const lineStyles = {};
|
|
||||||
|
|
||||||
configurations.forEach(config => {
|
|
||||||
const { color, lines: lineNumbers } = config;
|
|
||||||
lineNumbers.forEach(lineNumber => {
|
|
||||||
lineStyles[lineNumber] = color;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to escape HTML characters
|
|
||||||
function escapeHtml(text) {
|
|
||||||
return text.replace(/[&<>"'`=\/]/g, function(s) {
|
|
||||||
return "&#" + s.charCodeAt(0) + ";";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process each line
|
|
||||||
const processedLines = lines.map((line, index) => {
|
|
||||||
const lineNumber = index + 1; // Line numbers start at 1
|
|
||||||
const escapedLine = escapeHtml(line);
|
|
||||||
const color = lineStyles[lineNumber];
|
|
||||||
|
|
||||||
if (color) {
|
|
||||||
// Wrap the line in a span with inline style
|
|
||||||
return `<span style="background-color: ${color}">${escapedLine}</span>`;
|
|
||||||
} else {
|
|
||||||
return escapedLine;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Join the lines back together
|
|
||||||
const newContent = processedLines.join('\n');
|
|
||||||
|
|
||||||
// Set the new content as HTML
|
|
||||||
$pre.html(newContent);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
$.fn.miniTabs = function(tabsConfig, options) {
|
|
||||||
const settings = {
|
|
||||||
tabClass: 'minitab',
|
|
||||||
tabsContainerClass: 'minitabs',
|
|
||||||
activeClass: 'active',
|
|
||||||
...(options || {})
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.each(function() {
|
|
||||||
const $wrapper = $(this);
|
|
||||||
const $contents = $wrapper.find('div[id]').hide();
|
|
||||||
const $tabsContainer = $('<div>', { class: settings.tabsContainerClass }).prependTo($wrapper);
|
|
||||||
|
|
||||||
// Generate tabs
|
|
||||||
Object.entries(tabsConfig).forEach(([tabTitle, contentSelector], index) => {
|
|
||||||
const $content = $wrapper.find(contentSelector);
|
|
||||||
if (index === 0) $content.show(); // Show first content by default
|
|
||||||
|
|
||||||
$('<a>', {
|
|
||||||
class: `${settings.tabClass}${index === 0 ? ` ${settings.activeClass}` : ''}`,
|
|
||||||
text: tabTitle,
|
|
||||||
'data-target': contentSelector
|
|
||||||
}).appendTo($tabsContainer);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tab click event
|
|
||||||
$tabsContainer.on('click', `.${settings.tabClass}`, function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const $tab = $(this);
|
|
||||||
const target = $tab.data('target');
|
|
||||||
|
|
||||||
// Update active tab
|
|
||||||
$tabsContainer.find(`.${settings.tabClass}`).removeClass(settings.activeClass);
|
|
||||||
$tab.addClass(settings.activeClass);
|
|
||||||
|
|
||||||
// Show/hide content
|
|
||||||
$contents.hide();
|
|
||||||
$wrapper.find(target).show();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Object to store ongoing requests by namespace
|
|
||||||
const requests = {};
|
|
||||||
|
|
||||||
$.abortiveSingularAjax = function(options) {
|
|
||||||
const namespace = options.namespace || 'default';
|
|
||||||
|
|
||||||
// Abort the current request in this namespace if it's still ongoing
|
|
||||||
if (requests[namespace]) {
|
|
||||||
requests[namespace].abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new AJAX request and store its reference in the correct namespace
|
|
||||||
requests[namespace] = $.ajax(options);
|
|
||||||
|
|
||||||
// Return the current request in case it's needed
|
|
||||||
return requests[namespace];
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
||||||
@@ -1,63 +1,53 @@
|
|||||||
function redirectToVersion(version) {
|
function redirect_to_version(version) {
|
||||||
var currentUrl = window.location.href.split('?')[0]; // Base URL without query parameters
|
var currentUrl = window.location.href;
|
||||||
|
var baseUrl = currentUrl.split('?')[0]; // Base URL without query parameters
|
||||||
var anchor = '';
|
var anchor = '';
|
||||||
|
|
||||||
// Check if there is an anchor
|
// Check if there is an anchor
|
||||||
if (currentUrl.indexOf('#') !== -1) {
|
if (baseUrl.indexOf('#') !== -1) {
|
||||||
anchor = currentUrl.substring(currentUrl.indexOf('#'));
|
anchor = baseUrl.substring(baseUrl.indexOf('#'));
|
||||||
currentUrl = currentUrl.substring(0, currentUrl.indexOf('#'));
|
baseUrl = baseUrl.substring(0, baseUrl.indexOf('#'));
|
||||||
}
|
}
|
||||||
|
window.location.href = baseUrl + '?version=' + version + anchor;
|
||||||
window.location.href = currentUrl + '?version=' + version + anchor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupDateWidget() {
|
document.addEventListener('keydown', function (event) {
|
||||||
$(document).on('keydown', function (event) {
|
var selectElement = document.getElementById('preview-version');
|
||||||
var $selectElement = $('#preview-version');
|
if (selectElement) {
|
||||||
var $selectedOption = $selectElement.find('option:selected');
|
var selectedOption = selectElement.querySelector('option:checked');
|
||||||
|
if (selectedOption) {
|
||||||
if ($selectedOption.length) {
|
if (event.key === 'ArrowLeft') {
|
||||||
if (event.key === 'ArrowLeft' && $selectedOption.prev().length) {
|
if (selectedOption.previousElementSibling) {
|
||||||
redirectToVersion($selectedOption.prev().val());
|
redirect_to_version(selectedOption.previousElementSibling.value);
|
||||||
} else if (event.key === 'ArrowRight' && $selectedOption.next().length) {
|
}
|
||||||
redirectToVersion($selectedOption.next().val());
|
} else if (event.key === 'ArrowRight') {
|
||||||
|
if (selectedOption.nextElementSibling) {
|
||||||
|
redirect_to_version(selectedOption.nextElementSibling.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
$('#preview-version').on('change', function () {
|
|
||||||
redirectToVersion($(this).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
var $selectedOption = $('#preview-version option:selected');
|
|
||||||
|
|
||||||
if ($selectedOption.length) {
|
|
||||||
var $prevOption = $selectedOption.prev();
|
|
||||||
var $nextOption = $selectedOption.next();
|
|
||||||
|
|
||||||
if ($prevOption.length) {
|
|
||||||
$('#btn-previous').attr('href', '?version=' + $prevOption.val());
|
|
||||||
} else {
|
|
||||||
$('#btn-previous').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($nextOption.length) {
|
|
||||||
$('#btn-next').attr('href', '?version=' + $nextOption.val());
|
|
||||||
} else {
|
|
||||||
$('#btn-next').remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
if ($('#preview-version').length) {
|
|
||||||
setupDateWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#diff-col > pre').highlightLines([
|
|
||||||
{
|
|
||||||
'color': '#ee0000',
|
|
||||||
'lines': triggered_line_numbers
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('preview-version').addEventListener('change', function () {
|
||||||
|
redirect_to_version(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
var selectElement = document.getElementById('preview-version');
|
||||||
|
if (selectElement) {
|
||||||
|
var selectedOption = selectElement.querySelector('option:checked');
|
||||||
|
if (selectedOption) {
|
||||||
|
if (selectedOption.previousElementSibling) {
|
||||||
|
document.getElementById('btn-previous').href = "?version=" + selectedOption.previousElementSibling.value;
|
||||||
|
} else {
|
||||||
|
document.getElementById('btn-previous').remove()
|
||||||
|
}
|
||||||
|
if (selectedOption.nextElementSibling) {
|
||||||
|
document.getElementById('btn-next').href = "?version=" + selectedOption.nextElementSibling.value;
|
||||||
|
} else {
|
||||||
|
document.getElementById('btn-next').remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,25 @@ function toggleOpacity(checkboxSelector, fieldSelector, inverted) {
|
|||||||
checkbox.addEventListener('change', updateOpacity);
|
checkbox.addEventListener('change', updateOpacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
// Object to store ongoing requests by namespace
|
||||||
|
const requests = {};
|
||||||
|
|
||||||
|
$.abortiveSingularAjax = function(options) {
|
||||||
|
const namespace = options.namespace || 'default';
|
||||||
|
|
||||||
|
// Abort the current request in this namespace if it's still ongoing
|
||||||
|
if (requests[namespace]) {
|
||||||
|
requests[namespace].abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new AJAX request and store its reference in the correct namespace
|
||||||
|
requests[namespace] = $.ajax(options);
|
||||||
|
|
||||||
|
// Return the current request in case it's needed
|
||||||
|
return requests[namespace];
|
||||||
|
};
|
||||||
|
})(jQuery);
|
||||||
|
|
||||||
function request_textpreview_update() {
|
function request_textpreview_update() {
|
||||||
if (!$('body').hasClass('preview-text-enabled')) {
|
if (!$('body').hasClass('preview-text-enabled')) {
|
||||||
@@ -23,7 +42,7 @@ function request_textpreview_update() {
|
|||||||
$('textarea:visible, input:visible').each(function () {
|
$('textarea:visible, input:visible').each(function () {
|
||||||
const $element = $(this); // Cache the jQuery object for the current element
|
const $element = $(this); // Cache the jQuery object for the current element
|
||||||
const name = $element.attr('name'); // Get the name attribute of the element
|
const name = $element.attr('name'); // Get the name attribute of the element
|
||||||
data[name] = $element.is(':checkbox') ? ($element.is(':checked') ? $element.val() : false) : $element.val();
|
data[name] = $element.is(':checkbox') ? ($element.is(':checked') ? $element.val() : undefined) : $element.val();
|
||||||
});
|
});
|
||||||
|
|
||||||
$.abortiveSingularAjax({
|
$.abortiveSingularAjax({
|
||||||
@@ -32,19 +51,7 @@ function request_textpreview_update() {
|
|||||||
data: data,
|
data: data,
|
||||||
namespace: 'watchEdit'
|
namespace: 'watchEdit'
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
$('#filters-and-triggers #text-preview-before-inner').text(data['before_filter']);
|
$('#filters-and-triggers #text-preview-inner').text(data);
|
||||||
|
|
||||||
$('#filters-and-triggers #text-preview-inner')
|
|
||||||
.text(data['after_filter'])
|
|
||||||
.highlightLines([
|
|
||||||
{
|
|
||||||
'color': '#ee0000',
|
|
||||||
'lines': data['trigger_line_numbers']
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}).fail(function (error) {
|
}).fail(function (error) {
|
||||||
if (error.statusText === 'abort') {
|
if (error.statusText === 'abort') {
|
||||||
console.log('Request was aborted due to a new request being fired.');
|
console.log('Request was aborted due to a new request being fired.');
|
||||||
@@ -71,7 +78,6 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
||||||
$("#text-preview-inner").css('max-height', (vh-300)+"px");
|
$("#text-preview-inner").css('max-height', (vh-300)+"px");
|
||||||
$("#text-preview-before-inner").css('max-height', (vh-300)+"px");
|
|
||||||
|
|
||||||
// Realtime preview of 'Filters & Text' setup
|
// Realtime preview of 'Filters & Text' setup
|
||||||
var debounced_request_textpreview_update = request_textpreview_update.debounce(100);
|
var debounced_request_textpreview_update = request_textpreview_update.debounce(100);
|
||||||
@@ -86,9 +92,6 @@ $(document).ready(function () {
|
|||||||
$('input:visible')[method]('keyup blur change', debounced_request_textpreview_update);
|
$('input:visible')[method]('keyup blur change', debounced_request_textpreview_update);
|
||||||
$("#filters-and-triggers-tab")[method]('click', debounced_request_textpreview_update);
|
$("#filters-and-triggers-tab")[method]('click', debounced_request_textpreview_update);
|
||||||
});
|
});
|
||||||
$('.minitabs-wrapper').miniTabs({
|
|
||||||
"Content after filters": "#text-preview-inner",
|
|
||||||
"Content raw/before filters": "#text-preview-before-inner"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -42,19 +42,12 @@
|
|||||||
|
|
||||||
|
|
||||||
#browser-steps .flex-wrapper {
|
#browser-steps .flex-wrapper {
|
||||||
|
font-size: 80%;
|
||||||
|
|
||||||
|
@media screen and (min-width: 800px) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
font-size: 80%;
|
|
||||||
#browser-steps-ui {
|
|
||||||
flex-grow: 1; /* Allow it to grow and fill the available space */
|
|
||||||
flex-shrink: 1; /* Allow it to shrink if needed */
|
|
||||||
flex-basis: 0; /* Start with 0 base width so it stretches as much as possible */
|
|
||||||
background-color: #eee;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#browser-steps-fieldlist {
|
#browser-steps-fieldlist {
|
||||||
flex-grow: 0; /* Don't allow it to grow */
|
flex-grow: 0; /* Don't allow it to grow */
|
||||||
flex-shrink: 0; /* Don't allow it to shrink */
|
flex-shrink: 0; /* Don't allow it to shrink */
|
||||||
@@ -63,6 +56,23 @@
|
|||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#browser-steps-ui {
|
||||||
|
flex-grow: 1; /* Allow it to grow and fill the available space */
|
||||||
|
flex-shrink: 1; /* Allow it to shrink if needed */
|
||||||
|
flex-basis: 0; /* Start with 0 base width so it stretches as much as possible */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#browser-steps-ui {
|
||||||
|
background-color: #eee;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#browser-steps-field-list {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this is duplicate :( */
|
/* this is duplicate :( */
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
.minitabs-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
> div[id] {
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minitabs {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minitab {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 12px 0;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #333;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-bottom: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minitab:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minitab.active {
|
|
||||||
background-color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
@import "minitabs";
|
|
||||||
|
|
||||||
body.preview-text-enabled {
|
body.preview-text-enabled {
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
#filters-and-triggers > div {
|
#filters-and-triggers > div {
|
||||||
display: flex; /* Establishes Flexbox layout */
|
display: flex; /* Establishes Flexbox layout */
|
||||||
gap: 20px; /* Adds space between the columns */
|
gap: 20px; /* Adds space between the columns */
|
||||||
position: relative; /* Ensures the sticky positioning is relative to this parent */
|
position: relative; /* Ensures the sticky positioning is relative to this parent */
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* layout of the page */
|
/* layout of the page */
|
||||||
#edit-text-filter, #text-preview {
|
#edit-text-filter, #text-preview {
|
||||||
@@ -24,24 +19,18 @@ body.preview-text-enabled {
|
|||||||
|
|
||||||
#text-preview {
|
#text-preview {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 20px;
|
top: 25px;
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#activate-text-preview {
|
|
||||||
background-color: var(--color-grey-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* actual preview area */
|
/* actual preview area */
|
||||||
.monospace-preview {
|
#text-preview-inner {
|
||||||
background: var(--color-background-input);
|
background: var(--color-grey-900);
|
||||||
border: 1px solid var(--color-grey-600);
|
border: 1px solid var(--color-grey-600);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: var(--color-text-input);
|
color: #333;
|
||||||
font-family: "Courier New", Courier, monospace; /* Sets the font to a monospace type */
|
font-family: "Courier New", Courier, monospace; /* Sets the font to a monospace type */
|
||||||
font-size: 70%;
|
font-size: 12px;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
white-space: pre-wrap; /* Preserves whitespace and line breaks like <pre> */
|
white-space: pre-wrap; /* Preserves whitespace and line breaks like <pre> */
|
||||||
overflow-wrap: break-word; /* Allows long words to break and wrap to the next line */
|
overflow-wrap: break-word; /* Allows long words to break and wrap to the next line */
|
||||||
@@ -51,6 +40,6 @@ body.preview-text-enabled {
|
|||||||
#activate-text-preview {
|
#activate-text-preview {
|
||||||
right: 0;
|
right: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 0;
|
||||||
box-shadow: 1px 1px 4px var(--color-shadow-jump);
|
box-shadow: 1px 1px 4px var(--color-shadow-jump);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,10 @@ a.pure-button-selected {
|
|||||||
background: var(--color-background-button-cancel);
|
background: var(--color-background-button-cancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#save_button {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
li {
|
li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -617,9 +621,9 @@ footer {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: flex;
|
>* {
|
||||||
align-items: center;
|
display: inline-block;
|
||||||
gap: 1em;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,12 +683,6 @@ footer {
|
|||||||
tr {
|
tr {
|
||||||
th {
|
th {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
// Hide the "Last" text for smaller screens
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.hide-on-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.empty-cell {
|
.empty-cell {
|
||||||
@@ -700,24 +698,6 @@ footer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody {
|
|
||||||
tr {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
// The third child of each row will take up the remaining space
|
|
||||||
// This is useful for the URL column, which should expand to fill the remaining space
|
|
||||||
:nth-child(3) {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
// The last three children (from the end) of each row will take up the full width
|
|
||||||
// This is useful for the "Last Checked", "Last Changed", and the action buttons columns, which should each take up the full width
|
|
||||||
:nth-last-child(-n+3) {
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-checked {
|
.last-checked {
|
||||||
>span {
|
>span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@@ -836,11 +816,6 @@ textarea::placeholder {
|
|||||||
- We dont use 'size' with <input> because `size` is too unreliable to override, and will often push-out
|
- We dont use 'size' with <input> because `size` is too unreliable to override, and will often push-out
|
||||||
- Rely always on width in CSS
|
- Rely always on width in CSS
|
||||||
*/
|
*/
|
||||||
/** Set max width for input field */
|
|
||||||
.m-d {
|
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 761px) {
|
@media only screen and (min-width: 761px) {
|
||||||
|
|
||||||
/* m-d is medium-desktop */
|
/* m-d is medium-desktop */
|
||||||
@@ -956,13 +931,6 @@ body.full-width {
|
|||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make action buttons have consistent size and spacing */
|
|
||||||
#actions .pure-control-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.625em;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-form-message-inline {
|
.pure-form-message-inline {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
color: var(--color-text-input-description);
|
color: var(--color-text-input-description);
|
||||||
@@ -1006,28 +974,6 @@ ul {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 760px) {
|
|
||||||
.time-check-widget {
|
|
||||||
tbody {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr auto 1fr;
|
|
||||||
gap: 0.625em 0.3125em;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
display: contents;
|
|
||||||
th {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
input[type="number"] {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@import "parts/_visualselector";
|
@import "parts/_visualselector";
|
||||||
|
|
||||||
#webdriver_delay {
|
#webdriver_delay {
|
||||||
|
|||||||
@@ -47,19 +47,12 @@
|
|||||||
display: none; }
|
display: none; }
|
||||||
|
|
||||||
#browser-steps .flex-wrapper {
|
#browser-steps .flex-wrapper {
|
||||||
|
font-size: 80%; }
|
||||||
|
@media screen and (min-width: 800px) {
|
||||||
|
#browser-steps .flex-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
height: 70vh;
|
height: 70vh; }
|
||||||
font-size: 80%; }
|
|
||||||
#browser-steps .flex-wrapper #browser-steps-ui {
|
|
||||||
flex-grow: 1;
|
|
||||||
/* Allow it to grow and fill the available space */
|
|
||||||
flex-shrink: 1;
|
|
||||||
/* Allow it to shrink if needed */
|
|
||||||
flex-basis: 0;
|
|
||||||
/* Start with 0 base width so it stretches as much as possible */
|
|
||||||
background-color: #eee;
|
|
||||||
border-radius: 5px; }
|
|
||||||
#browser-steps .flex-wrapper #browser-steps-fieldlist {
|
#browser-steps .flex-wrapper #browser-steps-fieldlist {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
/* Don't allow it to grow */
|
/* Don't allow it to grow */
|
||||||
@@ -71,6 +64,18 @@
|
|||||||
/* Set a max width to prevent overflow */
|
/* Set a max width to prevent overflow */
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
overflow-y: scroll; }
|
overflow-y: scroll; }
|
||||||
|
#browser-steps .flex-wrapper #browser-steps-ui {
|
||||||
|
flex-grow: 1;
|
||||||
|
/* Allow it to grow and fill the available space */
|
||||||
|
flex-shrink: 1;
|
||||||
|
/* Allow it to shrink if needed */
|
||||||
|
flex-basis: 0;
|
||||||
|
/* Start with 0 base width so it stretches as much as possible */ } }
|
||||||
|
#browser-steps .flex-wrapper #browser-steps-ui {
|
||||||
|
background-color: #eee;
|
||||||
|
border-radius: 5px; }
|
||||||
|
#browser-steps .flex-wrapper #browser-steps-field-list {
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
/* this is duplicate :( */
|
/* this is duplicate :( */
|
||||||
#browsersteps-selector-wrapper {
|
#browsersteps-selector-wrapper {
|
||||||
@@ -428,43 +433,16 @@ html[data-darkmode="true"] #toggle-light-mode .icon-dark {
|
|||||||
fill: #ff0000 !important;
|
fill: #ff0000 !important;
|
||||||
transition: all ease 0.3s !important; }
|
transition: all ease 0.3s !important; }
|
||||||
|
|
||||||
.minitabs-wrapper {
|
|
||||||
width: 100%; }
|
|
||||||
.minitabs-wrapper > div[id] {
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-top: none; }
|
|
||||||
.minitabs-wrapper .minitabs {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid #ccc; }
|
|
||||||
.minitabs-wrapper .minitab {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 12px 0;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #333;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-bottom: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s; }
|
|
||||||
.minitabs-wrapper .minitab:hover {
|
|
||||||
background-color: #ddd; }
|
|
||||||
.minitabs-wrapper .minitab.active {
|
|
||||||
background-color: #fff;
|
|
||||||
font-weight: bold; }
|
|
||||||
|
|
||||||
body.preview-text-enabled {
|
body.preview-text-enabled {
|
||||||
/* layout of the page */
|
/* layout of the page */
|
||||||
/* actual preview area */ }
|
/* actual preview area */ }
|
||||||
@media (min-width: 800px) {
|
|
||||||
body.preview-text-enabled #filters-and-triggers > div {
|
body.preview-text-enabled #filters-and-triggers > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
/* Establishes Flexbox layout */
|
/* Establishes Flexbox layout */
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
/* Adds space between the columns */
|
/* Adds space between the columns */
|
||||||
position: relative;
|
position: relative;
|
||||||
/* Ensures the sticky positioning is relative to this parent */ } }
|
/* Ensures the sticky positioning is relative to this parent */ }
|
||||||
body.preview-text-enabled #edit-text-filter, body.preview-text-enabled #text-preview {
|
body.preview-text-enabled #edit-text-filter, body.preview-text-enabled #text-preview {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
/* Each column takes an equal amount of available space */
|
/* Each column takes an equal amount of available space */
|
||||||
@@ -474,20 +452,16 @@ body.preview-text-enabled {
|
|||||||
display: none; }
|
display: none; }
|
||||||
body.preview-text-enabled #text-preview {
|
body.preview-text-enabled #text-preview {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 20px;
|
top: 25px;
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
display: block !important; }
|
display: block !important; }
|
||||||
body.preview-text-enabled #activate-text-preview {
|
body.preview-text-enabled #text-preview-inner {
|
||||||
background-color: var(--color-grey-500); }
|
background: var(--color-grey-900);
|
||||||
body.preview-text-enabled .monospace-preview {
|
|
||||||
background: var(--color-background-input);
|
|
||||||
border: 1px solid var(--color-grey-600);
|
border: 1px solid var(--color-grey-600);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: var(--color-text-input);
|
color: #333;
|
||||||
font-family: "Courier New", Courier, monospace;
|
font-family: "Courier New", Courier, monospace;
|
||||||
/* Sets the font to a monospace type */
|
/* Sets the font to a monospace type */
|
||||||
font-size: 70%;
|
font-size: 12px;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
/* Preserves whitespace and line breaks like <pre> */
|
/* Preserves whitespace and line breaks like <pre> */
|
||||||
@@ -497,7 +471,7 @@ body.preview-text-enabled {
|
|||||||
#activate-text-preview {
|
#activate-text-preview {
|
||||||
right: 0;
|
right: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 0;
|
||||||
box-shadow: 1px 1px 4px var(--color-shadow-jump); }
|
box-shadow: 1px 1px 4px var(--color-shadow-jump); }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -723,6 +697,9 @@ a.pure-button-selected {
|
|||||||
.button-cancel {
|
.button-cancel {
|
||||||
background: var(--color-background-button-cancel); }
|
background: var(--color-background-button-cancel); }
|
||||||
|
|
||||||
|
#save_button {
|
||||||
|
margin-right: 1rem; }
|
||||||
|
|
||||||
.messages li {
|
.messages li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
@@ -921,10 +898,8 @@ footer {
|
|||||||
.pure-form .inline-radio ul {
|
.pure-form .inline-radio ul {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
list-style: none; }
|
list-style: none; }
|
||||||
.pure-form .inline-radio ul li {
|
.pure-form .inline-radio ul li > * {
|
||||||
display: flex;
|
display: inline-block; }
|
||||||
align-items: center;
|
|
||||||
gap: 1em; }
|
|
||||||
|
|
||||||
@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px) {
|
@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px) {
|
||||||
.box {
|
.box {
|
||||||
@@ -960,24 +935,12 @@ footer {
|
|||||||
.watch-table thead {
|
.watch-table thead {
|
||||||
display: block; }
|
display: block; }
|
||||||
.watch-table thead tr th {
|
.watch-table thead tr th {
|
||||||
display: inline-block; } }
|
display: inline-block; }
|
||||||
@media only screen and (max-width: 760px) and (max-width: 768px), (min-device-width: 768px) and (max-device-width: 800px) and (max-width: 768px) {
|
|
||||||
.watch-table thead tr th .hide-on-mobile {
|
|
||||||
display: none; } }
|
|
||||||
|
|
||||||
@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 800px) {
|
|
||||||
.watch-table thead .empty-cell {
|
.watch-table thead .empty-cell {
|
||||||
display: none; }
|
display: none; }
|
||||||
.watch-table tbody td,
|
.watch-table tbody td,
|
||||||
.watch-table tbody tr {
|
.watch-table tbody tr {
|
||||||
display: block; }
|
display: block; }
|
||||||
.watch-table tbody tr {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap; }
|
|
||||||
.watch-table tbody tr :nth-child(3) {
|
|
||||||
flex-grow: 1; }
|
|
||||||
.watch-table tbody tr :nth-last-child(-n+3) {
|
|
||||||
flex-basis: 100%; }
|
|
||||||
.watch-table .last-checked > span {
|
.watch-table .last-checked > span {
|
||||||
vertical-align: middle; }
|
vertical-align: middle; }
|
||||||
.watch-table .last-checked::before {
|
.watch-table .last-checked::before {
|
||||||
@@ -1069,10 +1032,6 @@ textarea::placeholder {
|
|||||||
- We dont use 'size' with <input> because `size` is too unreliable to override, and will often push-out
|
- We dont use 'size' with <input> because `size` is too unreliable to override, and will often push-out
|
||||||
- Rely always on width in CSS
|
- Rely always on width in CSS
|
||||||
*/
|
*/
|
||||||
/** Set max width for input field */
|
|
||||||
.m-d {
|
|
||||||
min-width: 100%; }
|
|
||||||
|
|
||||||
@media only screen and (min-width: 761px) {
|
@media only screen and (min-width: 761px) {
|
||||||
/* m-d is medium-desktop */
|
/* m-d is medium-desktop */
|
||||||
.m-d {
|
.m-d {
|
||||||
@@ -1133,8 +1092,7 @@ body.full-width .edit-form {
|
|||||||
.edit-form {
|
.edit-form {
|
||||||
min-width: 70%;
|
min-width: 70%;
|
||||||
/* so it cant overflow */
|
/* so it cant overflow */
|
||||||
max-width: 95%;
|
max-width: 95%; }
|
||||||
/* Make action buttons have consistent size and spacing */ }
|
|
||||||
.edit-form .box-wrap {
|
.edit-form .box-wrap {
|
||||||
position: relative; }
|
position: relative; }
|
||||||
.edit-form .inner {
|
.edit-form .inner {
|
||||||
@@ -1143,10 +1101,6 @@ body.full-width .edit-form {
|
|||||||
.edit-form #actions {
|
.edit-form #actions {
|
||||||
display: block;
|
display: block;
|
||||||
background: var(--color-background); }
|
background: var(--color-background); }
|
||||||
.edit-form #actions .pure-control-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.625em;
|
|
||||||
flex-wrap: wrap; }
|
|
||||||
.edit-form .pure-form-message-inline {
|
.edit-form .pure-form-message-inline {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
color: var(--color-text-input-description); }
|
color: var(--color-text-input-description); }
|
||||||
@@ -1175,21 +1129,6 @@ ul {
|
|||||||
.time-check-widget tr input[type="number"] {
|
.time-check-widget tr input[type="number"] {
|
||||||
width: 5em; }
|
width: 5em; }
|
||||||
|
|
||||||
@media only screen and (max-width: 760px) {
|
|
||||||
.time-check-widget tbody {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr auto 1fr;
|
|
||||||
gap: 0.625em 0.3125em;
|
|
||||||
align-items: center; }
|
|
||||||
.time-check-widget tr {
|
|
||||||
display: contents; }
|
|
||||||
.time-check-widget tr th {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 5px; }
|
|
||||||
.time-check-widget tr input[type="number"] {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 5em; } }
|
|
||||||
|
|
||||||
#selector-wrapper {
|
#selector-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
const watch_visual_selector_data_url="{{url_for('static_content', group='visual_selector_data', filename=uuid)}}";
|
const watch_visual_selector_data_url="{{url_for('static_content', group='visual_selector_data', filename=uuid)}}";
|
||||||
const default_system_fetch_backend="{{ settings_application['fetch_backend'] }}";
|
const default_system_fetch_backend="{{ settings_application['fetch_backend'] }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="{{url_for('static_content', group='js', filename='plugins.js')}}" defer></script>
|
|
||||||
<script src="{{url_for('static_content', group='js', filename='watch-settings.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='watch-settings.js')}}" defer></script>
|
||||||
<script src="{{url_for('static_content', group='js', filename='limit.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='limit.js')}}" defer></script>
|
||||||
<script src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
|
||||||
@@ -371,10 +371,10 @@ nav
|
|||||||
") }}
|
") }}
|
||||||
<span class="pure-form-message-inline">
|
<span class="pure-form-message-inline">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Matching text will be <strong>removed</strong> from the text snapshot</li>
|
|
||||||
<li>Each line processed separately, any line matching will be ignored (removed before creating the checksum)</li>
|
<li>Each line processed separately, any line matching will be ignored (removed before creating the checksum)</li>
|
||||||
<li>Regular Expression support, wrap the entire line in forward slash <code>/regex/</code></li>
|
<li>Regular Expression support, wrap the entire line in forward slash <code>/regex/</code></li>
|
||||||
<li>Changing this will affect the comparison checksum which may trigger an alert</li>
|
<li>Changing this will affect the comparison checksum which may trigger an alert</li>
|
||||||
|
<li>Use the preview/show current tab to see ignores</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -422,21 +422,14 @@ Unavailable") }}
|
|||||||
<script>
|
<script>
|
||||||
const preview_text_edit_filters_url="{{url_for('watch_get_preview_rendered', uuid=uuid)}}";
|
const preview_text_edit_filters_url="{{url_for('watch_get_preview_rendered', uuid=uuid)}}";
|
||||||
</script>
|
</script>
|
||||||
<br>
|
<span><strong>Preview of the text that is used for changedetection after all filters run.</strong></span><br>
|
||||||
{#<div id="text-preview-controls"><span id="text-preview-refresh" class="pure-button button-xsmall">Refresh</span></div>#}
|
{#<div id="text-preview-controls"><span id="text-preview-refresh" class="pure-button button-xsmall">Refresh</span></div>#}
|
||||||
|
<p>
|
||||||
<div class="minitabs-wrapper">
|
<div id="text-preview-inner"></div>
|
||||||
<div id="text-preview-inner" class="monospace-preview">
|
</p>
|
||||||
<p>Loading...</p>
|
|
||||||
</div>
|
|
||||||
<div id="text-preview-before-inner" style="display: none;" class="monospace-preview">
|
|
||||||
<p>Loading...</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# rendered sub Template #}
|
{# rendered sub Template #}
|
||||||
{% if extra_form_content %}
|
{% if extra_form_content %}
|
||||||
|
|||||||
@@ -3,13 +3,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<script>
|
<script>
|
||||||
const screenshot_url = "{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
const screenshot_url = "{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
||||||
const triggered_line_numbers = {{ triggered_line_numbers|tojson }};
|
|
||||||
{% if last_error_screenshot %}
|
{% if last_error_screenshot %}
|
||||||
const error_screenshot_url = "{{url_for('static_content', group='screenshot', filename=uuid, error_screenshot=1) }}";
|
const error_screenshot_url = "{{url_for('static_content', group='screenshot', filename=uuid, error_screenshot=1) }}";
|
||||||
{% endif %}
|
{% endif %}
|
||||||
const highlight_submit_ignore_url = "{{url_for('highlight_submit_ignore_url', uuid=uuid)}}";
|
const highlight_submit_ignore_url = "{{url_for('highlight_submit_ignore_url', uuid=uuid)}}";
|
||||||
</script>
|
</script>
|
||||||
<script src="{{url_for('static_content', group='js', filename='plugins.js')}}"></script>
|
|
||||||
<script src="{{ url_for('static_content', group='js', filename='diff-overview.js') }}" defer></script>
|
<script src="{{ url_for('static_content', group='js', filename='diff-overview.js') }}" defer></script>
|
||||||
<script src="{{ url_for('static_content', group='js', filename='preview.js') }}" defer></script>
|
<script src="{{ url_for('static_content', group='js', filename='preview.js') }}" defer></script>
|
||||||
<script src="{{ url_for('static_content', group='js', filename='tabs.js') }}" defer></script>
|
<script src="{{ url_for('static_content', group='js', filename='tabs.js') }}" defer></script>
|
||||||
@@ -69,15 +67,16 @@
|
|||||||
|
|
||||||
<div class="tab-pane-inner" id="text">
|
<div class="tab-pane-inner" id="text">
|
||||||
<div class="snapshot-age">{{ current_version|format_timestamp_timeago }}</div>
|
<div class="snapshot-age">{{ current_version|format_timestamp_timeago }}</div>
|
||||||
|
<span class="ignored">Grey lines are ignored</span> <span class="triggered">Blue lines are triggers</span>
|
||||||
<span class="tip"><strong>Pro-tip</strong>: Highlight text to add to ignore filters</span>
|
<span class="tip"><strong>Pro-tip</strong>: Highlight text to add to ignore filters</span>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td id="diff-col" class="highlightable-filter">
|
<td id="diff-col" class="highlightable-filter">
|
||||||
<pre style="border-left: 2px solid #ddd;">
|
{% for row in content %}
|
||||||
{{ content }}
|
<div class="{{ row.classes }}">{{ row.line }}</div>
|
||||||
</pre>
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -172,11 +172,11 @@ nav
|
|||||||
<span class="pure-form-message-inline">Note: This is applied globally in addition to the per-watch rules.</span><br>
|
<span class="pure-form-message-inline">Note: This is applied globally in addition to the per-watch rules.</span><br>
|
||||||
<span class="pure-form-message-inline">
|
<span class="pure-form-message-inline">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Matching text will be <strong>removed</strong> from the text snapshot</li>
|
|
||||||
<li>Note: This is applied globally in addition to the per-watch rules.</li>
|
<li>Note: This is applied globally in addition to the per-watch rules.</li>
|
||||||
<li>Each line processed separately, any line matching will be ignored (removed before creating the checksum)</li>
|
<li>Each line processed separately, any line matching will be ignored (removed before creating the checksum)</li>
|
||||||
<li>Regular Expression support, wrap the entire line in forward slash <code>/regex/</code></li>
|
<li>Regular Expression support, wrap the entire line in forward slash <code>/regex/</code></li>
|
||||||
<li>Changing this will affect the comparison checksum which may trigger an alert</li>
|
<li>Changing this will affect the comparison checksum which may trigger an alert</li>
|
||||||
|
<li>Use the preview/show current tab to see ignores</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -78,8 +78,8 @@
|
|||||||
{% if any_has_restock_price_processor %}
|
{% if any_has_restock_price_processor %}
|
||||||
<th>Restock & Price</th>
|
<th>Restock & Price</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">Last</span> Checked <span class='arrow {{link_order}}'></span></a></th>
|
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}">Last Checked <span class='arrow {{link_order}}'></span></a></th>
|
||||||
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">Last</span> Changed <span class='arrow {{link_order}}'></span></a></th>
|
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}">Last Changed <span class='arrow {{link_order}}'></span></a></th>
|
||||||
<th class="empty-cell"></th>
|
<th class="empty-cell"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -191,9 +191,9 @@
|
|||||||
{% if watch.history_n >= 2 %}
|
{% if watch.history_n >= 2 %}
|
||||||
|
|
||||||
{% if is_unviewed %}
|
{% if is_unviewed %}
|
||||||
<a href="{{ url_for('diff_history_page', uuid=watch.uuid, from_version=watch.get_next_snapshot_key_to_last_viewed) }}" target="{{watch.uuid}}" class="pure-button pure-button-primary diff-link">History</a>
|
<a href="{{ url_for('diff_history_page', uuid=watch.uuid, from_version=watch.get_next_snapshot_key_to_last_viewed) }}" target="{{watch.uuid}}" class="pure-button pure-button-primary diff-link">Diff</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ url_for('diff_history_page', uuid=watch.uuid)}}" target="{{watch.uuid}}" class="pure-button pure-button-primary diff-link">History</a>
|
<a href="{{ url_for('diff_history_page', uuid=watch.uuid)}}" target="{{watch.uuid}}" class="pure-button pure-button-primary diff-link">Diff</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def test_select_custom(client, live_server, measure_memory_usage):
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
# We should see something via proxy
|
# We should see something via proxy
|
||||||
assert b' - 0.' in res.data
|
assert b'<div class=""> - 0.' in res.data
|
||||||
|
|
||||||
#
|
#
|
||||||
# Now we should see the request in the container logs for "squid-squid-custom" because it will be the only default
|
# Now we should see the request in the container logs for "squid-squid-custom" because it will be the only default
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ def test_setup(client, live_server, measure_memory_usage):
|
|||||||
live_server_setup(live_server)
|
live_server_setup(live_server)
|
||||||
|
|
||||||
def test_check_removed_line_contains_trigger(client, live_server, measure_memory_usage):
|
def test_check_removed_line_contains_trigger(client, live_server, measure_memory_usage):
|
||||||
#live_server_setup(live_server)
|
|
||||||
# Give the endpoint time to spin up
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
set_original()
|
set_original()
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
@@ -151,9 +152,7 @@ def test_check_add_line_contains_trigger(client, live_server, measure_memory_usa
|
|||||||
|
|
||||||
# A line thats not the trigger should not trigger anything
|
# A line thats not the trigger should not trigger anything
|
||||||
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
|
|
||||||
assert b'1 watches queued for rechecking.' in res.data
|
assert b'1 watches queued for rechecking.' in res.data
|
||||||
|
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
assert b'unviewed' not in res.data
|
assert b'unviewed' not in res.data
|
||||||
|
|||||||
@@ -115,9 +115,9 @@ def test_check_filter_multiline(client, live_server, measure_memory_usage):
|
|||||||
# Plaintext that doesnt look like a regex should match also
|
# Plaintext that doesnt look like a regex should match also
|
||||||
assert b'and this should be' in res.data
|
assert b'and this should be' in res.data
|
||||||
|
|
||||||
assert b'Something' in res.data
|
assert b'<div class="">Something' in res.data
|
||||||
assert b'across 6 billion multiple' in res.data
|
assert b'<div class="">across 6 billion multiple' in res.data
|
||||||
assert b'lines' in res.data
|
assert b'<div class="">lines' in res.data
|
||||||
|
|
||||||
# but the last one, which also says 'lines' shouldnt be here (non-greedy match checking)
|
# but the last one, which also says 'lines' shouldnt be here (non-greedy match checking)
|
||||||
assert b'aaand something lines' not in res.data
|
assert b'aaand something lines' not in res.data
|
||||||
@@ -183,19 +183,20 @@ def test_check_filter_and_regex_extract(client, live_server, measure_memory_usag
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
|
|
||||||
assert b'1000 online' in res.data
|
# Class will be blank for now because the frontend didnt apply the diff
|
||||||
|
assert b'<div class="">1000 online' in res.data
|
||||||
|
|
||||||
# All regex matching should be here
|
# All regex matching should be here
|
||||||
assert b'2000 online' in res.data
|
assert b'<div class="">2000 online' in res.data
|
||||||
|
|
||||||
# Both regexs should be here
|
# Both regexs should be here
|
||||||
assert b'80 guests' in res.data
|
assert b'<div class="">80 guests' in res.data
|
||||||
|
|
||||||
# Regex with flag handling should be here
|
# Regex with flag handling should be here
|
||||||
assert b'SomeCase insensitive 3456' in res.data
|
assert b'<div class="">SomeCase insensitive 3456' in res.data
|
||||||
|
|
||||||
# Singular group from /somecase insensitive (345\d)/i
|
# Singular group from /somecase insensitive (345\d)/i
|
||||||
assert b'3456' in res.data
|
assert b'<div class="">3456' in res.data
|
||||||
|
|
||||||
# Regex with multiline flag handling should be here
|
# Regex with multiline flag handling should be here
|
||||||
|
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ def set_modified_ignore_response():
|
|||||||
f.write(test_return_data)
|
f.write(test_return_data)
|
||||||
|
|
||||||
|
|
||||||
# Ignore text now just removes it entirely, is a LOT more simpler code this way
|
|
||||||
|
|
||||||
def test_check_ignore_text_functionality(client, live_server, measure_memory_usage):
|
def test_check_ignore_text_functionality(client, live_server, measure_memory_usage):
|
||||||
|
|
||||||
# Use a mix of case in ZzZ to prove it works case-insensitive.
|
# Use a mix of case in ZzZ to prove it works case-insensitive.
|
||||||
ignore_text = "XXXXX\r\nYYYYY\r\nzZzZZ\r\nnew ignore stuff"
|
ignore_text = "XXXXX\r\nYYYYY\r\nzZzZZ\r\nnew ignore stuff"
|
||||||
set_original_ignore_response()
|
set_original_ignore_response()
|
||||||
|
|
||||||
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
@@ -151,10 +151,12 @@ def test_check_ignore_text_functionality(client, live_server, measure_memory_usa
|
|||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
assert b'unviewed' in res.data
|
assert b'unviewed' in res.data
|
||||||
|
|
||||||
|
# Check the preview/highlighter, we should be able to see what we ignored, but it should be highlighted
|
||||||
|
# We only introduce the "modified" content that includes what we ignore so we can prove the newest version also displays
|
||||||
|
# at /preview
|
||||||
res = client.get(url_for("preview_page", uuid="first"))
|
res = client.get(url_for("preview_page", uuid="first"))
|
||||||
|
# We should be able to see what we ignored
|
||||||
# Should no longer be in the preview
|
assert b'<div class="ignored">new ignore stuff' in res.data
|
||||||
assert b'new ignore stuff' not in res.data
|
|
||||||
|
|
||||||
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
||||||
assert b'Deleted' in res.data
|
assert b'Deleted' in res.data
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def set_original_ignore_response():
|
|||||||
f.write(test_return_data)
|
f.write(test_return_data)
|
||||||
|
|
||||||
|
|
||||||
def test_ignore(client, live_server, measure_memory_usage):
|
def test_highlight_ignore(client, live_server, measure_memory_usage):
|
||||||
live_server_setup(live_server)
|
live_server_setup(live_server)
|
||||||
set_original_ignore_response()
|
set_original_ignore_response()
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
@@ -51,9 +51,9 @@ def test_ignore(client, live_server, measure_memory_usage):
|
|||||||
# Should return a link
|
# Should return a link
|
||||||
assert b'href' in res.data
|
assert b'href' in res.data
|
||||||
|
|
||||||
# It should not be in the preview anymore
|
# And it should register in the preview page
|
||||||
res = client.get(url_for("preview_page", uuid=uuid))
|
res = client.get(url_for("preview_page", uuid=uuid))
|
||||||
assert b'<div class="ignored">oh yeah 456' not in res.data
|
assert b'<div class="ignored">oh yeah 456' in res.data
|
||||||
|
|
||||||
# Should be in base.html
|
# Should be in base.html
|
||||||
assert b'csrftoken' in res.data
|
assert b'csrftoken' in res.data
|
||||||
@@ -499,7 +499,7 @@ def test_correct_header_detect(client, live_server, measure_memory_usage):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert b'"hello": 123,' in res.data
|
assert b'"hello": 123,' in res.data
|
||||||
assert b'"world": 123' in res.data
|
assert b'"world": 123</div>' in res.data
|
||||||
|
|
||||||
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
||||||
assert b'Deleted' in res.data
|
assert b'Deleted' in res.data
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from .util import live_server_setup, wait_for_all_checks
|
from . util import live_server_setup
|
||||||
|
|
||||||
|
|
||||||
def set_original_ignore_response():
|
def set_original_ignore_response():
|
||||||
@@ -59,9 +59,12 @@ def test_trigger_functionality(client, live_server, measure_memory_usage):
|
|||||||
|
|
||||||
live_server_setup(live_server)
|
live_server_setup(live_server)
|
||||||
|
|
||||||
|
sleep_time_for_fetch_thread = 3
|
||||||
trigger_text = "Add to cart"
|
trigger_text = "Add to cart"
|
||||||
set_original_ignore_response()
|
set_original_ignore_response()
|
||||||
|
|
||||||
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
@@ -86,14 +89,14 @@ def test_trigger_functionality(client, live_server, measure_memory_usage):
|
|||||||
)
|
)
|
||||||
assert b"Updated watch." in res.data
|
assert b"Updated watch." in res.data
|
||||||
|
|
||||||
wait_for_all_checks(client)
|
|
||||||
# Check it saved
|
# Check it saved
|
||||||
res = client.get(
|
res = client.get(
|
||||||
url_for("edit_page", uuid="first"),
|
url_for("edit_page", uuid="first"),
|
||||||
)
|
)
|
||||||
assert bytes(trigger_text.encode('utf-8')) in res.data
|
assert bytes(trigger_text.encode('utf-8')) in res.data
|
||||||
|
|
||||||
|
# Give the thread time to pick it up
|
||||||
|
time.sleep(sleep_time_for_fetch_thread)
|
||||||
|
|
||||||
# so that we set the state to 'unviewed' after all the edits
|
# so that we set the state to 'unviewed' after all the edits
|
||||||
client.get(url_for("diff_history_page", uuid="first"))
|
client.get(url_for("diff_history_page", uuid="first"))
|
||||||
@@ -101,7 +104,8 @@ def test_trigger_functionality(client, live_server, measure_memory_usage):
|
|||||||
# Trigger a check
|
# Trigger a check
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
|
|
||||||
wait_for_all_checks(client)
|
# Give the thread time to pick it up
|
||||||
|
time.sleep(sleep_time_for_fetch_thread)
|
||||||
|
|
||||||
# It should report nothing found (no new 'unviewed' class)
|
# It should report nothing found (no new 'unviewed' class)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
@@ -113,17 +117,19 @@ def test_trigger_functionality(client, live_server, measure_memory_usage):
|
|||||||
|
|
||||||
# Trigger a check
|
# Trigger a check
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
wait_for_all_checks(client)
|
# Give the thread time to pick it up
|
||||||
|
time.sleep(sleep_time_for_fetch_thread)
|
||||||
|
|
||||||
# It should report nothing found (no new 'unviewed' class)
|
# It should report nothing found (no new 'unviewed' class)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
assert b'unviewed' not in res.data
|
assert b'unviewed' not in res.data
|
||||||
|
|
||||||
# Now set the content which contains the trigger text
|
# Now set the content which contains the trigger text
|
||||||
|
time.sleep(sleep_time_for_fetch_thread)
|
||||||
set_modified_with_trigger_text_response()
|
set_modified_with_trigger_text_response()
|
||||||
|
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
wait_for_all_checks(client)
|
time.sleep(sleep_time_for_fetch_thread)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
assert b'unviewed' in res.data
|
assert b'unviewed' in res.data
|
||||||
|
|
||||||
@@ -136,7 +142,4 @@ def test_trigger_functionality(client, live_server, measure_memory_usage):
|
|||||||
res = client.get(url_for("preview_page", uuid="first"))
|
res = client.get(url_for("preview_page", uuid="first"))
|
||||||
|
|
||||||
# We should be able to see what we triggered on
|
# We should be able to see what we triggered on
|
||||||
# The JS highlighter should tell us which lines (also used in the live-preview)
|
assert b'<div class="triggered">Add to cart' in res.data
|
||||||
assert b'const triggered_line_numbers = [6]' in res.data
|
|
||||||
assert b'Add to cart' in res.data
|
|
||||||
|
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ def test_check_xpath_text_function_utf8(client, live_server, measure_memory_usag
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
|
|
||||||
assert b'Stock Alert (UK): RPi CM4' in res.data
|
assert b'<div class="">Stock Alert (UK): RPi CM4' in res.data
|
||||||
assert b'Stock Alert (UK): Big monitor' in res.data
|
assert b'<div class="">Stock Alert (UK): Big monitor' in res.data
|
||||||
|
|
||||||
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
||||||
assert b'Deleted' in res.data
|
assert b'Deleted' in res.data
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ class update_worker(threading.Thread):
|
|||||||
|
|
||||||
update_handler.call_browser()
|
update_handler.call_browser()
|
||||||
|
|
||||||
changed_detected, update_obj, contents = update_handler.run_changedetection(
|
changed_detected, update_obj, contents, content_after_filters = update_handler.run_changedetection(
|
||||||
watch=watch,
|
watch=watch,
|
||||||
skip_when_checksum_same=skip_when_same_checksum,
|
skip_when_checksum_same=skip_when_same_checksum,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,10 +58,6 @@ services:
|
|||||||
#
|
#
|
||||||
# Absolute minimum seconds to recheck, overrides any watch minimum, change to 0 to disable
|
# Absolute minimum seconds to recheck, overrides any watch minimum, change to 0 to disable
|
||||||
# - MINIMUM_SECONDS_RECHECK_TIME=3
|
# - MINIMUM_SECONDS_RECHECK_TIME=3
|
||||||
#
|
|
||||||
# If you want to watch local files file:///path/to/file.txt (careful! security implications!)
|
|
||||||
# - ALLOW_FILE_URI=False
|
|
||||||
|
|
||||||
# Comment out ports: when using behind a reverse proxy , enable networks: etc.
|
# Comment out ports: when using behind a reverse proxy , enable networks: etc.
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
|
|||||||
@@ -93,5 +93,3 @@ babel
|
|||||||
|
|
||||||
# Needed for > 3.10, https://github.com/microsoft/playwright-python/issues/2096
|
# Needed for > 3.10, https://github.com/microsoft/playwright-python/issues/2096
|
||||||
greenlet >= 3.0.3
|
greenlet >= 3.0.3
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user