Compare commits

..

40 Commits

Author SHA1 Message Date
dgtlmoon
6fb0621263 More translation updates 2026-01-03 14:46:34 +01:00
dgtlmoon
de5d5c1d86 More translation improvements 2026-01-03 14:33:59 +01:00
dgtlmoon
d85842d1cd Improving translations 2026-01-03 14:24:28 +01:00
dgtlmoon
0e58ffdc82 More styling improvements 2026-01-03 13:48:07 +01:00
dgtlmoon
7f03a3e132 Fix styling 2026-01-03 13:27:18 +01:00
dgtlmoon
85737694f9 Adding toast libraries 2026-01-03 13:19:18 +01:00
dgtlmoon
40cae6fc42 Adding toast 2026-01-03 13:18:56 +01:00
dgtlmoon
060f9a806d Fixing mobile mobile menu and adding action menu 2026-01-03 12:59:31 +01:00
dgtlmoon
9627c63695 Adding test sidebar 2026-01-03 12:33:23 +01:00
dgtlmoon
cedabf4ff6 Language set redirect - keep hash
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2026-01-03 01:59:42 +01:00
dgtlmoon
03116fef8f Adding small test for switching modes (#3701)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
2026-01-02 19:51:21 +01:00
dgtlmoon
b1257dd196 UI - Handling redirects on login to the correct page (#3699) 2026-01-02 17:46:25 +01:00
dgtlmoon
7e61f5b663 more resilient same UUID being processed (#3700) 2026-01-02 17:46:12 +01:00
dgtlmoon
afa8451448 Puppeteer - Improvements to timeout handling 2026-01-02 17:45:41 +01:00
dgtlmoon
b5023a6fda Adding flash() translations (#3698) 2026-01-02 16:41:31 +01:00
dgtlmoon
895368144f Localising flags 2026-01-02 15:14:18 +01:00
dgtlmoon
9096407fcb Multi-language / Translations support (#3696) 2026-01-02 15:01:22 +01:00
dgtlmoon
df8f86ccbf Fix template discovery path 2026-01-02 14:49:27 +01:00
dgtlmoon
40dc3fef7e Difference - Fixing test for extract-text
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2026-01-02 11:22:43 +01:00
dgtlmoon
5f4998960d Puppeteer - Spelling mistake in log output 2026-01-02 11:22:31 +01:00
dependabot[bot]
7a515c4202 Bump cryptography from 44.0.1 to 46.0.3 (#3589)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-31 01:06:06 +01:00
dgtlmoon
48e21226a1 UI - Add modal alert/confirmations on delete/clear #3598 #3382 (#3693)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-30 19:03:26 +01:00
dgtlmoon
cdf34bf614 CSS/JS For image comparison 2025-12-30 18:17:33 +01:00
dgtlmoon
a94560190f Adding new Processor - Image / screenshot comparison (disabled for this release) (#3680) 2025-12-30 18:03:52 +01:00
dgtlmoon
fefaf40514 UI - Add https:// to URL on quickwatch form if not present
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-29 14:52:03 +01:00
dgtlmoon
6f66c39628 Requests - cleanup should be async function 2025-12-29 14:51:56 +01:00
dgtlmoon
eb0f83b45b Puppeteer fetcher - Better shutdown/cleanup handling (#3692)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
2025-12-29 10:24:01 +01:00
dependabot[bot]
f2284f7a9b Update flask-socketio requirement from ~=5.5.1 to ~=5.6.0 (#3691) 2025-12-29 10:23:00 +01:00
dependabot[bot]
4b0ad525f3 Update brotli requirement from ~=1.1 to ~=1.2 (#3687)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-22 10:19:46 +01:00
dgtlmoon
a748a43224 "History" page - Use faster server side "difference" rendering, show ignored/triggered rows (#3442)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-15 15:39:07 +01:00
dependabot[bot]
acfcaf42d4 Update lxml requirement (#3590) 2025-12-15 15:38:12 +01:00
dependabot[bot]
6158bb48b8 Update pytest requirement from ~=7.2 to ~=9.0 (#3676)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-15 11:46:33 +01:00
dependabot[bot]
d4fc1a3b6e Bump the all group with 3 updates (#3678) 2025-12-15 11:45:54 +01:00
dependabot[bot]
f39b5e5a46 Update jsonschema requirement from ~=4.0 to ~=4.25 (#3618)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-15 00:04:32 +01:00
dgtlmoon
30ba603956 UI - 'Recheck all' should return back to the correct group/tag (#3673)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-12-11 17:24:29 +01:00
dependabot[bot]
3147c5a3e2 Update pluggy requirement from ~=1.5 to ~=1.6 (#3616) 2025-12-11 17:16:30 +01:00
dgtlmoon
f599efacab Pluggable content fetchers (#3653) 2025-12-11 17:16:14 +01:00
dgtlmoon
d7dbc50d70 UI - Notification error text output fix #3669 #3280 (#3672) 2025-12-11 16:57:06 +01:00
dgtlmoon
51bb358ea7 Improving dev workflow 2025-11-28 16:20:11 +01:00
dgtlmoon
fe4df1d41f 'dev' container should be only built on 'dev' branch 2025-11-28 16:16:23 +01:00
50 changed files with 490 additions and 5445 deletions

View File

@@ -183,9 +183,6 @@ docker compose pull && docker compose up -d
See the wiki for more information https://github.com/dgtlmoon/changedetection.io/wiki
## Different browser viewport sizes (mobile, desktop etc)
If you are using the recommended `sockpuppetbrowser` (which is in the docker-compose.yml as a setting to be uncommented) you can easily set different viewport sizes for your web page change detection, [see more information here about setting up different viewport sizes](https://github.com/dgtlmoon/sockpuppetbrowser?tab=readme-ov-file#setting-viewport-size).
## Filters

View File

@@ -2,7 +2,7 @@
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
# Semver means never use .01, or 00. Should be .1.
__version__ = '0.52.1'
__version__ = '0.51.4'
from changedetectionio.strtobool import strtobool
from json.decoder import JSONDecodeError

View File

@@ -64,17 +64,8 @@ class Watch(Resource):
@validate_openapi_request('getWatch')
def get(self, uuid):
"""Get information about a single watch, recheck, pause, or mute."""
import time
from copy import deepcopy
watch = None
for _ in range(20):
try:
watch = deepcopy(self.datastore.data['watching'].get(uuid))
break
except RuntimeError:
# Incase dict changed, try again
time.sleep(0.01)
watch = deepcopy(self.datastore.data['watching'].get(uuid))
if not watch:
abort(404, message='No watch exists with the UUID of {}'.format(uuid))
@@ -311,28 +302,18 @@ class WatchHistoryDiff(Resource):
from_version_file_contents = watch.get_history_snapshot(from_timestamp)
to_version_file_contents = watch.get_history_snapshot(to_timestamp)
# Get diff preferences from query parameters (matching UI preferences in DIFF_PREFERENCES_CONFIG)
# Support both 'type' (UI parameter) and 'word_diff' (API parameter) for backward compatibility
diff_type = request.args.get('type', 'diffLines')
if diff_type == 'diffWords':
word_diff = True
# Get diff preferences (using defaults similar to the existing code)
diff_prefs = {
'diff_ignoreWhitespace': False,
'diff_changesOnly': True
}
# Get boolean diff preferences with defaults from DIFF_PREFERENCES_CONFIG
changes_only = strtobool(request.args.get('changesOnly', 'true'))
ignore_whitespace = strtobool(request.args.get('ignoreWhitespace', 'false'))
include_removed = strtobool(request.args.get('removed', 'true'))
include_added = strtobool(request.args.get('added', 'true'))
include_replaced = strtobool(request.args.get('replaced', 'true'))
# Generate the diff with all preferences
# Generate the diff
content = diff.render_diff(
previous_version_file_contents=from_version_file_contents,
newest_version_file_contents=to_version_file_contents,
ignore_junk=ignore_whitespace,
include_equal=changes_only,
include_removed=include_removed,
include_added=include_added,
include_replaced=include_replaced,
ignore_junk=diff_prefs.get('diff_ignoreWhitespace'),
include_equal=not diff_prefs.get('diff_changesOnly'),
word_diff=word_diff,
)

View File

@@ -85,7 +85,9 @@
<div class="tab-pane-inner" id="notifications">
<fieldset>
{{ render_common_settings_form(form.application.form, emailprefix, settings_application, extra_notification_token_placeholder_info) }}
<div class="field-group">
{{ render_common_settings_form(form.application.form, emailprefix, settings_application, extra_notification_token_placeholder_info) }}
</div>
</fieldset>
<div class="pure-control-group" id="notification-base-url">
{{ render_field(form.application.form.base_url, class="m-d") }}
@@ -126,7 +128,7 @@
</div>
<div class="pure-control-group">
{{ render_field(form.requests.form.timeout) }}
<span class="pure-form-message-inline">For regular plain requests (not chrome based), maximum number of seconds until timeout, 1-999.</span><br>
<span class="pure-form-message-inline">For regular plain requests (not chrome based), maximum number of seconds until timeout, 1-999.<br>
</div>
<div class="pure-control-group inline-radio">
{{ render_field(form.requests.form.default_ua) }}
@@ -217,7 +219,7 @@ nav
<a id="chrome-extension-link"
title="Try our new Chrome Extension!"
href="https://chromewebstore.google.com/detail/changedetectionio-website/kefcfmgmlhmankjmnbijimhofdjekbop">
<img alt="Chrome store icon" src="{{ url_for('static_content', group='images', filename='google-chrome-icon.png') }}" >
<img alt="Chrome store icon" src="{{ url_for('static_content', group='images', filename='google-chrome-icon.png') }}" alt="Chrome">
Chrome Webstore
</a>
</p>
@@ -258,14 +260,14 @@ nav
Ensure the settings below are correct, they are used to manage the time schedule for checking your web page watches.
</div>
<div class="pure-control-group">
<p><strong>UTC Time &amp; Date from Server:</strong> <span id="utc-time" >{{ utc_time }}</span></p>
<p><strong>Local Time &amp; Date in Browser:</strong> <span class="local-time" data-utc="{{ utc_time }}"></span></p>
<div>
<p><strong>UTC Time &amp Date from Server:</strong> <span id="utc-time" >{{ utc_time }}</span></p>
<p><strong>Local Time &amp Date in Browser:</strong> <span class="local-time" data-utc="{{ utc_time }}"></span></p>
<p>
{{ render_field(form.application.form.scheduler_timezone_default) }}
<datalist id="timezones" style="display: none;">
{%- for timezone in available_timezones -%}<option value="{{ timezone }}">{{ timezone }}</option>{%- endfor -%}
</datalist>
</div>
</p>
</div>
</div>
<div class="tab-pane-inner" id="ui-options">
@@ -334,7 +336,7 @@ nav
</div>
</div>
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.</p>
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.
<div class="pure-control-group" id="extra-proxies-setting">
{{ render_fieldlist_with_inline_errors(form.requests.form.extra_proxies) }}

View File

@@ -118,7 +118,6 @@ def construct_blueprint(datastore: ChangeDetectionStore):
sent_obj = process_notification(n_object, datastore)
except Exception as e:
logger.error(e)
e_str = str(e)
# Remove this text which is not important and floods the container
e_str = e_str.replace(

View File

@@ -87,7 +87,7 @@
</form>
</div>
<div id="diff-jump" style="display:none;"><!-- disabled for now -->
<div id="diff-jump">
<a id="jump-next-diff" title="{{ _('Jump to next difference') }}">{{ _('Jump') }}</a>
</div>

View File

@@ -86,7 +86,6 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
datastore=datastore,
errored_count=errored_count,
form=form,
generate_tag_colors=processors.generate_processor_badge_colors,
guid=datastore.data['app_guid'],
has_proxies=datastore.proxy_list,
hosted_sticky=os.getenv("SALTED_PASS", False) == False,

View File

@@ -22,33 +22,6 @@ document.addEventListener('DOMContentLoaded', function() {
/* Auto-generated processor badge colors */
{{ processor_badge_css|safe }}
/* Auto-generated tag colors */
{%- for uuid, tag in tags -%}
{%- if tag and tag.title -%}
{%- set class_name = tag.title|sanitize_tag_class -%}
{%- set colors = generate_tag_colors(tag.title) -%}
.button-tag.tag-{{ class_name }} {
background-color: {{ colors['light']['bg'] }};
color: {{ colors['light']['color'] }};
}
.watch-tag-list.tag-{{ class_name }} {
background-color: {{ colors['light']['bg'] }};
color: {{ colors['light']['color'] }};
}
html[data-darkmode="true"] .button-tag.tag-{{ class_name }} {
background-color: {{ colors['dark']['bg'] }};
color: {{ colors['dark']['color'] }};
}
html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
background-color: {{ colors['dark']['bg'] }};
color: {{ colors['dark']['color'] }};
}
{%- endif -%}
{%- endfor -%}
</style>
<div class="box" id="form-quick-watch-add">
@@ -109,7 +82,7 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
<!-- tag list -->
{%- for uuid, tag in tags -%}
{%- if tag != "" -%}
<a href="{{url_for('watchlist.index', tag=uuid) }}" class="pure-button button-tag tag-{{ tag.title|sanitize_tag_class }} {{'active' if active_tag_uuid == uuid }}">{{ tag.title }}</a>
<a href="{{url_for('watchlist.index', tag=uuid) }}" class="pure-button button-tag {{'active' if active_tag_uuid == uuid }}">{{ tag.title }}</a>
{%- endif -%}
{%- endfor -%}
</div>
@@ -196,7 +169,7 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
<div class="flex-wrapper">
{% if 'favicons_enabled' not in ui_settings or ui_settings['favicons_enabled'] %}
<div>{# A page might have hundreds of these images, set IMG options for lazy loading, don't set SRC if we dont have it so it doesnt fetch the placeholder' #}
<img alt="Favicon thumbnail" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src='data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="7.087" height="7.087" viewBox="0 0 7.087 7.087"%3E%3Ccircle cx="3.543" cy="3.543" r="3.279" stroke="%23e1e1e1" stroke-width="0.45" fill="none" opacity="0.74"/%3E%3C/svg%3E' {% endif %} >
<img alt="Favicon thumbnail" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src='data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="7.087" height="7.087" viewBox="0 0 7.087 7.087"%3E%3Ccircle cx="3.543" cy="3.543" r="3.279" stroke="%23e1e1e1" stroke-width="0.45" fill="none" opacity="0.74"/%3E%3C/svg%3E' {% endif %} />
</div>
{% endif %}
<div>
@@ -218,7 +191,7 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
<span class="processor-badge processor-badge-{{ watch['processor'] }}" title="{{ processor_descriptions.get(watch['processor'], watch['processor']) }}">{{ processor_badge_texts[watch['processor']] }}</span>
{%- endif -%}
{%- for watch_tag_uuid, watch_tag in datastore.get_all_tags_for_watch(watch['uuid']).items() -%}
<span class="watch-tag-list tag-{{ watch_tag.title|sanitize_tag_class }}">{{ watch_tag.title }}</span>
<span class="watch-tag-list">{{ watch_tag.title }}</span>
{%- endfor -%}
</div>
<div class="status-icons">

View File

@@ -297,25 +297,6 @@ def _jinja2_filter_fetcher_status_icons(fetcher_name):
return ''
@app.template_filter('sanitize_tag_class')
def _jinja2_filter_sanitize_tag_class(tag_title):
"""Sanitize a tag title to create a valid CSS class name.
Removes all non-alphanumeric characters and converts to lowercase.
Args:
tag_title: The tag title string
Returns:
str: A sanitized string suitable for use as a CSS class name
"""
import re
# Remove all non-alphanumeric characters and convert to lowercase
sanitized = re.sub(r'[^a-zA-Z0-9]', '', tag_title).lower()
# Ensure it starts with a letter (CSS requirement)
if sanitized and not sanitized[0].isalpha():
sanitized = 'tag' + sanitized
return sanitized if sanitized else 'tag'
# Import login_optionally_required from auth_decorator
from changedetectionio.auth_decorator import login_optionally_required
@@ -914,7 +895,7 @@ def notification_runner():
# At the moment only one thread runs (single runner)
n_object = notification_q.get(block=False)
except queue.Empty:
app.config.exit.wait(1)
time.sleep(1)
else:
@@ -951,7 +932,7 @@ def notification_runner():
app.config['watch_check_update_SIGNAL'].send(app_context=app, watch_uuid=n_object.get('uuid'))
# Process notifications
notification_debug_log+= ["{} - SENDING - {}".format(now.strftime("%c"), json.dumps(sent_obj))]
notification_debug_log+= ["{} - SENDING - {}".format(now.strftime("%Y/%m/%d %H:%M:%S,000"), json.dumps(sent_obj))]
# Trim the log length
notification_debug_log = notification_debug_log[-100:]
@@ -1009,7 +990,7 @@ def ticker_thread_check_time_launch_checks():
# Re #438 - Don't place more watches in the queue to be checked if the queue is already large
while update_q.qsize() >= 2000:
logger.warning(f"Recheck watches queue size limit reached ({MAX_QUEUE_SIZE}), skipping adding more items")
app.config.exit.wait(10.0)
time.sleep(3)
recheck_time_system_seconds = int(datastore.threshold_seconds)
@@ -1107,5 +1088,8 @@ def ticker_thread_check_time_launch_checks():
# Reset for next time
watch.jitter_seconds = 0
# Wait before checking the list again - saves CPU
time.sleep(1)
# Should be low so we can break this out in testing
app.config.exit.wait(1)

View File

@@ -781,8 +781,8 @@ class SingleBrowserStep(Form):
class processor_text_json_diff_form(commonSettingsForm):
url = fields.URLField('Web Page URL', validators=[validateURL()])
tags = StringTagUUID('Group Tag', [validators.Optional()], default='')
url = fields.URLField('URL', validators=[validateURL()])
tags = StringTagUUID('Group tag', [validators.Optional()], default='')
time_between_check = EnhancedFormField(
TimeBetweenCheckForm,

View File

@@ -34,16 +34,13 @@ def get_timeago_locale(flask_locale):
'no': 'nb_NO', # Norwegian Bokmål
'hi': 'in_HI', # Hindi
'cs': 'en', # Czech not supported by timeago, fallback to English
'en_GB': 'en', # British English - timeago uses 'en'
'en_US': 'en', # American English - timeago uses 'en'
}
return locale_map.get(flask_locale, flask_locale)
# Language metadata: flag icon CSS class and native name
# Using flag-icons library: https://flagicons.lipis.dev/
LANGUAGE_DATA = {
'en_GB': {'flag': 'fi fi-gb fis', 'name': 'English (UK)'},
'en_US': {'flag': 'fi fi-us fis', 'name': 'English (US)'},
'en': {'flag': 'fi fi-gb fis', 'name': 'English'},
'de': {'flag': 'fi fi-de fis', 'name': 'Deutsch'},
'fr': {'flag': 'fi fi-fr fis', 'name': 'Français'},
'ko': {'flag': 'fi fi-kr fis', 'name': '한국어'},
@@ -74,7 +71,10 @@ def get_available_languages():
"""
translations_dir = Path(__file__).parent / 'translations'
available = {}
# Always include English as base language
available = {
'en': LANGUAGE_DATA['en']
}
# Scan for translation directories
if translations_dir.exists():
@@ -85,10 +85,6 @@ def get_available_languages():
if po_file.exists():
available[lang_dir.name] = LANGUAGE_DATA[lang_dir.name]
# If no English variants found, fall back to adding en_GB as default
if 'en_GB' not in available and 'en_US' not in available:
available['en_GB'] = LANGUAGE_DATA['en_GB']
return available

View File

@@ -3,17 +3,17 @@
* Allows users to select their preferred language
*/
$(document).ready(function() {
const $languageButton = $('.language-selector');
const $languageModal = $('#language-modal');
const $closeButton = $('#close-language-modal');
document.addEventListener('DOMContentLoaded', function() {
const languageButton = document.getElementById('language-selector');
const languageModal = document.getElementById('language-modal');
const closeButton = document.getElementById('close-language-modal');
if (!$languageButton.length || !$languageModal.length) {
if (!languageButton || !languageModal) {
return;
}
// Open modal when language button is clicked
$languageButton.on('click', function(e) {
languageButton.addEventListener('click', function(e) {
e.preventDefault();
// Update all language links to include current hash in the redirect parameter
@@ -21,53 +21,51 @@ $(document).ready(function() {
const currentHash = window.location.hash;
if (currentHash) {
const $languageOptions = $languageModal.find('.language-option');
$languageOptions.each(function() {
const $option = $(this);
const url = new URL($option.attr('href'), window.location.origin);
const languageOptions = languageModal.querySelectorAll('.language-option');
languageOptions.forEach(function(option) {
const url = new URL(option.href, window.location.origin);
// Update the redirect parameter to include the hash
const redirectPath = currentPath + currentHash;
url.searchParams.set('redirect', redirectPath);
$option.attr('href', url.pathname + url.search + url.hash);
option.setAttribute('href', url.pathname + url.search + url.hash);
});
}
$languageModal[0].showModal();
languageModal.showModal();
});
// Close modal when cancel button is clicked
if ($closeButton.length) {
$closeButton.on('click', function() {
$languageModal[0].close();
if (closeButton) {
closeButton.addEventListener('click', function() {
languageModal.close();
});
}
// Close modal when clicking outside (on backdrop)
$languageModal.on('click', function(e) {
const rect = this.getBoundingClientRect();
languageModal.addEventListener('click', function(e) {
const rect = languageModal.getBoundingClientRect();
if (
e.clientY < rect.top ||
e.clientY > rect.bottom ||
e.clientX < rect.left ||
e.clientX > rect.right
) {
$languageModal[0].close();
languageModal.close();
}
});
// Close modal on Escape key
$languageModal.on('cancel', function(e) {
languageModal.addEventListener('cancel', function(e) {
e.preventDefault();
$languageModal[0].close();
languageModal.close();
});
// Highlight current language
const currentLocale = $('html').attr('lang') || 'en';
const $languageOptions = $languageModal.find('.language-option');
$languageOptions.each(function() {
const $option = $(this);
if ($option.attr('data-locale') === currentLocale) {
$option.addClass('active');
const currentLocale = document.documentElement.lang || 'en';
const languageOptions = languageModal.querySelectorAll('.language-option');
languageOptions.forEach(function(option) {
if (option.dataset.locale === currentLocale) {
option.classList.add('active');
}
});
});

View File

@@ -120,8 +120,7 @@ $(document).ready(function () {
console.log(`${data.event_timestamp} - Queue size update: ${data.q_length}`);
// Update queue bubble in action sidebar
//if (queueBubble) {
if (0) {
if (queueBubble) {
const count = parseInt(data.q_length) || 0;
const oldCount = parseInt(queueBubble.getAttribute('data-count')) || 0;

View File

@@ -3,12 +3,14 @@
* Toggles theme between light and dark mode.
*/
$(document).ready(function () {
const button = document.getElementById("toggle-light-mode");
$(".toggle-light-mode").on("click", function () {
const isDark = $("html").attr("data-darkmode") === "true";
$("html").attr("data-darkmode", !isDark);
setCookieValue(!isDark);
});
button.onclick = () => {
const htmlElement = document.getElementsByTagName("html");
const isDarkMode = htmlElement[0].dataset.darkmode === "true";
htmlElement[0].dataset.darkmode = !isDarkMode;
setCookieValue(!isDarkMode);
};
const setCookieValue = (value) => {
document.cookie = `css_dark_mode=${value};max-age=31536000;path=/`

View File

@@ -1,7 +0,0 @@
/**
* SCSS variables (compile-time)
* These can be used in media queries and other places where CSS custom properties don't work
*/
// Breakpoints
$desktop-wide-breakpoint: 980px;

View File

@@ -24,7 +24,7 @@
flex-direction: column;
gap: 0.5rem;
align-items: center;
z-index: 0;
z-index: 10;
@media only screen and (max-width: 900px) {
position: relative;
@@ -32,7 +32,7 @@
width: 100%;
flex-direction: row;
justify-content: space-around;
padding: 0;
padding: 1rem 0.5rem;
overflow-x: auto;
}
}

View File

@@ -1,13 +1,15 @@
.toggle-light-mode {
#toggle-light-mode {
/* width: 3rem;*/
/* default */
.icon-dark {
display: none;
}
}
html[data-darkmode="true"] {
.toggle-light-mode {
#toggle-light-mode {
.icon-light {
display: none;
}

View File

@@ -1,5 +1,4 @@
// Hamburger Menu for Mobile Navigation
@use "../settings" as *;
.hamburger-menu {
display: none;
@@ -10,7 +9,7 @@
z-index: 10001;
position: relative;
@media only screen and (max-width: $desktop-wide-breakpoint) {
@media only screen and (max-width: 768px) {
display: flex;
flex-direction: column;
justify-content: center;
@@ -98,7 +97,7 @@
li {
border-bottom: 1px solid var(--color-border-table-cell);
>* {
a {
display: block;
padding: 1rem 1.5rem;
color: var(--color-text);
@@ -136,9 +135,9 @@
margin-left: auto;
}
// Hide regular menu items on mobile (but not in mobile drawer)
@media only screen and (max-width: $desktop-wide-breakpoint) {
#top-right-menu .menu-collapsible {
// Hide regular menu items on mobile
@media only screen and (max-width: 768px) {
.menu-collapsible {
display: none !important;
}
@@ -152,7 +151,7 @@
}
// Desktop - hide mobile menu elements
@media only screen and (min-width: 1025px) {
@media only screen and (min-width: 769px) {
.hamburger-menu,
.mobile-menu-drawer,
.mobile-menu-overlay {

View File

@@ -1,69 +0,0 @@
#language-selector-flag {
display: inline-block;
width: 1.2em;
height: 1.2em;
vertical-align: middle;
border-radius: 50%;
overflow: hidden;
opacity: 0.6;
&:hover {
opacity: 1.0;
}
}
// Language Selector Modal Styles
.language-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem 0;
}
.language-option {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.25rem;
border-radius: 4px;
transition: background-color 0.2s ease;
text-decoration: none;
color: var(--color-text);
border: 1px solid transparent;
&:hover {
background-color: var(--color-background-menu-link-hover);
border-color: var(--color-border-table-cell);
}
&.active {
background-color: var(--color-link);
color: var(--color-text-button);
font-weight: 600;
}
.flag {
font-size: 1.5rem;
flex-shrink: 0;
}
.language-name {
flex-grow: 1;
font-size: 1rem;
}
}
.language-modal {
.language-list {
.lang-option {
display: inline-block;
width: 1.5em;
height: 1.5em;
vertical-align: middle;
margin-right: 0.5em;
border-radius: 50%;
overflow: hidden;
}
}
}

View File

@@ -20,8 +20,23 @@
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
// Subtle accent line at top
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(
90deg,
var(--color-link) 0%,
var(--color-menu-accent) 100%
);
}
&:hover {
transform: translateY(-2px);
box-shadow:
0 15px 50px rgba(0, 0, 0, 0.12),
0 5px 15px rgba(0, 0, 0, 0.06);
@@ -93,6 +108,7 @@
box-shadow: 0 2px 8px rgba(27, 152, 248, 0.2);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(27, 152, 248, 0.3);
background: #0066cc;
}

View File

@@ -31,13 +31,3 @@
}
}
}
#cdio-logo {
padding-left: 0.5em;
}
#inline-menu-extras-group {
>* {
display: inline-block;
}
}

View File

@@ -1,57 +0,0 @@
body.wrapped-tabs {
.tabs {
ul {
grid-template-columns: repeat(auto-fill, minmax(var(--tab-width, 180px), 1fr));
grid-auto-flow: row;
grid-auto-columns: unset;
gap: 0;
column-gap: 5px;
}
ul li {
border-radius: 0;
}
}
}
.tabs {
ul {
margin: 0px;
padding: 0px;
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
gap: 5px;
list-style: none;
li {
white-space: nowrap;
color: var(--color-text-tab);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: var(--color-background-tab);
&:not(.active) {
&:hover {
background-color: var(--color-background-tab-hover);
}
}
&.active,
:target {
background-color: var(--color-background);
a {
color: var(--color-text-tab-active);
font-weight: bold;
}
}
a {
display: block;
padding: 0.7em;
color: var(--color-text-tab);
}
}
}
}

View File

@@ -2,7 +2,6 @@
* -- BASE STYLES --
*/
@use "settings" as *;
@use "parts/variables";
@use "parts/arrows";
@use "parts/browser-steps";
@@ -24,14 +23,12 @@
@use "parts/widgets";
@use "parts/diff_image";
@use "parts/modal";
@use "parts/language";
@use "parts/action_sidebar";
@use "parts/hamburger_menu";
@use "parts/search_modal";
@use "parts/notification_bubble";
@use "parts/toast";
@use "parts/login_form";
@use "parts/tabs";
body {
@@ -160,13 +157,7 @@ body.spinner-active {
}
section.content {
@media only screen and (max-width: $desktop-wide-breakpoint) {
padding-top: 80px;
}
@media only screen and (min-width: $desktop-wide-breakpoint) {
padding-top: 100px;
}
padding-top: 100px;
padding-bottom: 1em;
flex-direction: column;
display: flex;
@@ -184,13 +175,13 @@ code {
border-radius: 5px;
padding: 2px 5px;
margin-right: 4px;
line-height: 1.2rem;
}
/* Processor type badges - colors auto-generated from processor names */
.processor-badge {
@extend .inline-tag;
font-weight: 900;
font-size: 0.85em;
font-weight: 500;
}
.watch-tag-list {
@@ -523,9 +514,6 @@ footer {
}
.sticky-tab {
@media only screen and (max-width: $desktop-wide-breakpoint) {
display: none;
}
position: absolute;
top: 60px;
font-size: 65%;
@@ -670,7 +658,7 @@ footer {
@media only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: $desktop-wide-breakpoint) {
(min-device-width: 768px) and (max-device-width: 1024px) {
.edit-form {
padding: 0.5em;
margin: 0;
@@ -682,10 +670,30 @@ footer {
}
@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: $desktop-wide-breakpoint) {
@media only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: 800px) {
div.sticky-tab#hosted-sticky {
top: 60px;
left: 0px;
right: auto;
}
section.content {
padding-top: 110px;
}
// Make the tabs easier to hit, they will be all nice and horizontal
div.tabs.collapsable ul li {
display: block;
border-radius: 0px;
margin-right: 0px;
}
input[type='text'] {
width: 100%;
}
}
.pure-table {
@@ -761,6 +769,45 @@ textarea::placeholder {
}
.tabs {
ul {
margin: 0px;
padding: 0px;
display: block;
li {
margin-right: 1px;
display: inline-block;
color: var(--color-text-tab);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: var(--color-background-tab);
&:not(.active) {
&:hover {
background-color: var(--color-background-tab-hover);
}
}
&.active,
:target {
background-color: var(--color-background);
a {
color: var(--color-text-tab-active);
font-weight: bold;
}
}
a {
display: block;
padding: 0.7em;
color: var(--color-text-tab);
}
}
}
}
$form-edge-padding: 20px;
.pure-form-stacked {
@@ -1097,4 +1144,44 @@ ul#highlightSnippetActions {
}
}
// Language Selector Modal Styles
.language-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem 0;
}
.language-option {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.25rem;
border-radius: 4px;
transition: background-color 0.2s ease;
text-decoration: none;
color: var(--color-text);
border: 1px solid transparent;
&:hover {
background-color: var(--color-background-menu-link-hover);
border-color: var(--color-border-table-cell);
}
&.active {
background-color: var(--color-link);
color: var(--color-text-button);
font-weight: 600;
}
.flag {
font-size: 1.5rem;
flex-shrink: 0;
}
.language-name {
flex-grow: 1;
font-size: 1rem;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -145,7 +145,6 @@
<div id="notification-test-log" style="display: none;"><span class="pure-form-message-inline">Processing..</span></div>
</div>
</div>
<div class="pure-control-group grey-form-border">
<div class="pure-control-group">
{{ render_field(form.notification_title, class="m-d notification-title", placeholder=settings_application['notification_title']) }}
@@ -170,7 +169,6 @@
</span></li>
</ul>
<br>
</div>
</div>
<div class="">
{{ render_field(form.notification_format , class="notification-format") }}

View File

@@ -53,10 +53,10 @@
<div class="home-menu pure-menu pure-menu-horizontal" id="nav-menu">
{% if has_password and not current_user.is_authenticated %}
<a id="cdio-logo" class="pure-menu-heading" href="https://changedetection.io" rel="noopener">
<a class="pure-menu-heading" href="https://changedetection.io" rel="noopener">
<strong>Change</strong>Detection.io</a>
{% else %}
<a id="cdio-logo" class="pure-menu-heading" href="{{url_for('watchlist.index')}}">
<a class="pure-menu-heading" href="{{url_for('watchlist.index')}}">
<strong>Change</strong>Detection.io</a>
{% endif %}
{% if current_diff_url and is_safe_valid_url(current_diff_url) %}
@@ -72,19 +72,61 @@
<ul class="pure-menu-list" id="top-right-menu">
<!-- Collapsible menu items (hidden on mobile, shown in drawer) -->
{% include "menu.html" %}
{% if current_user.is_authenticated or not has_password %}
{% if not current_diff_url %}
<li class="pure-menu-item menu-collapsible">
<button class="toggle-button" id="open-search-modal" type="button" title="{{ _('Search, or Use Alt+S Key') }}">
{% include "svgs/search-icon.svg" %}
</button>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('tags.') %}active{% endif %}">
<a href="{{ url_for('tags.tags_overview_page')}}" class="pure-menu-link">{{ _('GROUPS') }}</a>
</li>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('settings.') %}active{% endif %}">
<a href="{{ url_for('settings.settings_page')}}" class="pure-menu-link">{{ _('SETTINGS') }}</a>
</li>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('imports.') %}active{% endif %}">
<a href="{{ url_for('imports.import_page')}}" class="pure-menu-link">{{ _('IMPORT') }}</a>
</li>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('backups.') %}active{% endif %}">
<a href="{{ url_for('backups.index')}}" class="pure-menu-link">{{ _('BACKUPS') }}</a>
</li>
{% else %}
<li class="pure-menu-item menu-collapsible">
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=uuid, next='diff') }}" class="pure-menu-link">{{ _('EDIT') }}</a>
</li>
{% endif %}
{% else %}
<li class="pure-menu-item menu-collapsible">
<a class="pure-menu-link" href="https://changedetection.io">Website Change Detection and Notification.</a>
</li>
{% endif %}
{% if current_user.is_authenticated %}
<li class="pure-menu-item menu-collapsible">
<a href="{{url_for('logout', redirect=request.path)}}" class="pure-menu-link">{{ _('LOG OUT') }}</a>
</li>
{% endif %}
<!-- Always visible items -->
{% if current_user.is_authenticated or not has_password %}
<li class="pure-menu-item">
<button class="toggle-button" id="open-search-modal" type="button" title="{{ _('Search, or Use Alt+S Key') }}">
{% include "svgs/search-icon.svg" %}
</button>
</li>
{% endif %}
<li class="pure-menu-item">
<button class="toggle-button" id ="toggle-light-mode" type="button" title="{{ _('Toggle Light/Dark Mode') }}">
<span class="visually-hidden">{{ _('Toggle light/dark mode') }}</span>
<span class="icon-light">
{% include "svgs/light-mode-toggle-icon.svg" %}
</span>
<span class="icon-dark">
{% include "svgs/dark-mode-toggle-icon.svg" %}
</span>
</button>
</li>
<li class="pure-menu-item">
<button class="toggle-button" id="language-selector" type="button" title="{{ _('Change Language') }}">
<span class="visually-hidden">{{ _('Change language') }}</span>
<span class="{{ get_flag_for_locale(get_locale()) }}" style="display: inline-block; width: 1.2em; height: 1.2em; vertical-align: middle; border-radius: 50%; overflow: hidden;"></span>
</button>
</li>
<li class="pure-menu-item" id="heart-us">
<svg
fill="#ff0000"
@@ -94,9 +136,16 @@
id="svg-heart"
xmlns="http://www.w3.org/2000/svg"
>
<path id="heartpath" d="M 5.338316,0.50302766 C 0.71136983,0.50647126 -3.9576371,7.2707777 8.5004254,15.503028 23.833425,5.3700277 13.220206,-2.5384409 8.6762066,1.6475589 c -0.060791,0.054322 -0.11943,0.1110064 -0.1757812,0.1699219 -0.057,-0.059 -0.1157813,-0.116875 -0.1757812,-0.171875 C 7.4724566,0.86129334 6.4060729,0.50223298 5.338316,0.50302766 Z" style="fill:var(--color-background);fill-opacity:1;stroke:#ff0000;stroke-opacity:1" />
<path id="heartpath" d="M 5.338316,0.50302766 C 0.71136983,0.50647126 -3.9576371,7.2707777 8.5004254,15.503028 23.833425,5.3700277 13.220206,-2.5384409 8.6762066,1.6475589 c -0.060791,0.054322 -0.11943,0.1110064 -0.1757812,0.1699219 -0.057,-0.059 -0.1157813,-0.116875 -0.1757812,-0.171875 C 7.4724566,0.86129334 6.4060729,0.50223298 5.338316,0.50302766 Z"
style="fill:var(--color-background);fill-opacity:1;stroke:#ff0000;stroke-opacity:1" />
</svg>
</li>
<li class="pure-menu-item">
<a class="github-link" href="https://github.com/dgtlmoon/changedetection.io">
{% include "svgs/github.svg" %}
</a>
</li>
<!-- Hamburger menu button (mobile only) -->
<li class="pure-menu-item">
<button class="hamburger-menu" id="hamburger-toggle" aria-label="Toggle menu">
@@ -114,17 +163,27 @@
<div class="mobile-menu-overlay" id="mobile-menu-overlay"></div>
<div class="mobile-menu-drawer" id="mobile-menu-drawer">
<ul class="mobile-menu-items">
{% include "menu.html" %}
<li class="pure-menu-item menu-collapsible">
{%- if right_sticky -%}<div>{{ right_sticky }}</div>{%- endif -%}
<a href="https://changedetection.io/?ref={{ guid }}">Let us host your instance!</a><br>
</li>
{% if current_user.is_authenticated or not has_password %}
{% if not current_diff_url %}
<li><a href="{{ url_for('tags.tags_overview_page')}}">{{ _('GROUPS') }}</a></li>
<li><a href="{{ url_for('settings.settings_page')}}">{{ _('SETTINGS') }}</a></li>
<li><a href="{{ url_for('imports.import_page')}}">{{ _('IMPORT') }}</a></li>
<li><a href="{{ url_for('backups.index')}}">{{ _('BACKUPS') }}</a></li>
{% else %}
<li><a href="{{ url_for('ui.ui_edit.edit_page', uuid=uuid, next='diff') }}">{{ _('EDIT') }}</a></li>
{% endif %}
{% endif %}
{% if current_user.is_authenticated %}
<li><a href="{{url_for('logout', redirect=request.path)}}">{{ _('LOG OUT') }}</a></li>
{% endif %}
</ul>
</div>
<div id="pure-menu-horizontal-spinner"></div>
</div>
</div>
{% if hosted_sticky %}
<div class="sticky-tab" id="hosted-sticky">
<a href="https://changedetection.io/?ref={{guid}}">Let us host your instance!</a>
@@ -189,7 +248,6 @@
</div>
<div class="content-wrapper">
{#
{% if current_user.is_authenticated or not has_password %}
<aside class="action-sidebar">
<a href="{{ url_for('watchlist.index') }}" class="action-sidebar-item {% if request.endpoint.startswith('watchlist.') or request.endpoint.startswith('ui.') %}active{% endif %}" title="{{ _('Watch List') }}">
@@ -199,6 +257,7 @@
</svg>
<span class="action-label">{{ _('Watches') }}</span>
</a>
<a href="{{ url_for('queue_status') }}" class="action-sidebar-item {% if request.endpoint == 'queue_status' %}active{% endif %}" id="queue-action-item" title="{{ _('Queue Status') }}">
<svg class="action-icon" viewBox="0 0 24 24">
<line x1="8" y1="6" x2="21" y2="6"/>
@@ -211,9 +270,56 @@
<span class="action-label">{{ _('Queue') }}</span>
<span class="notification-bubble blue-bubble" id="queue-bubble" data-count="0"></span>
</a>
<a href="{{ url_for('settings.settings_page') }}" class="action-sidebar-item {% if request.endpoint.startswith('settings.') %}active{% endif %}" title="{{ _('Settings') }}">
<svg class="action-icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v6m0 6v10M3.5 3.5l4.2 4.2m5.6 5.6l4.2 4.2M1 12h6m6 0h10M3.5 20.5l4.2-4.2m5.6-5.6l4.2-4.2"/>
</svg>
<span class="action-label">{{ _('Settings') }}</span>
</a>
<a href="{{ url_for('backups.index') }}" class="action-sidebar-item {% if request.endpoint.startswith('backups.') %}active{% endif %}" title="{{ _('Backups') }}">
<svg class="action-icon" viewBox="0 0 24 24">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10 9 9 9 8 9"/>
</svg>
<span class="action-label">{{ _('Backups') }}</span>
</a>
<a href="#" class="action-sidebar-item" title="{{ _('Sitemap Crawler') }}">
<svg class="action-icon" viewBox="0 0 24 24">
<!-- Spider web with map nodes -->
<circle cx="12" cy="12" r="2"/>
<!-- Radial web lines -->
<line x1="12" y1="12" x2="12" y2="4"/>
<line x1="12" y1="12" x2="19" y2="7"/>
<line x1="12" y1="12" x2="20" y2="12"/>
<line x1="12" y1="12" x2="19" y2="17"/>
<line x1="12" y1="12" x2="12" y2="20"/>
<line x1="12" y1="12" x2="5" y2="17"/>
<line x1="12" y1="12" x2="4" y2="12"/>
<line x1="12" y1="12" x2="5" y2="7"/>
<!-- Outer web ring -->
<circle cx="12" cy="12" r="8" fill="none"/>
<!-- Map nodes on web -->
<circle cx="12" cy="4" r="1.5"/>
<circle cx="19" cy="7" r="1.5"/>
<circle cx="20" cy="12" r="1.5"/>
<circle cx="19" cy="17" r="1.5"/>
<circle cx="12" cy="20" r="1.5"/>
<circle cx="5" cy="17" r="1.5"/>
<circle cx="4" cy="12" r="1.5"/>
<circle cx="5" cy="7" r="1.5"/>
</svg>
<span class="action-label">{{ _('Sitemap') }}</span>
</a>
</aside>
{% endif %}
#}
<div class="content-main">
<header>
{% block header %}{% endblock %}
@@ -261,7 +367,7 @@
<div class="language-list">
{% for locale, lang_data in available_languages.items()|sort %}
<a href="{{ url_for('set_language', locale=locale, redirect=request.path) }}" class="language-option" data-locale="{{ locale }}">
<span class="lang-option {{ lang_data.flag }}"></span> <span class="language-name">{{ lang_data.name }}</span>
<span class="{{ lang_data.flag }}" style="display: inline-block; width: 1.5em; height: 1.5em; vertical-align: middle; margin-right: 0.5em; border-radius: 50%; overflow: hidden;"></span> <span class="language-name">{{ lang_data.name }}</span>
</a>
{% endfor %}
</div>
@@ -296,77 +402,6 @@
</dialog>
{% endif %}
<script>
(function() {
/* AUTOMATIC TAB COLUMN-IZER FOR WHEN TABS WRAP */
// Exit early if no tabs on page
if (!document.querySelector('.tab')) return;
const cache = new Map();
function checkWrapping(ul) {
const tabs = ul.querySelectorAll('.tab');
if (tabs.length < 2) return false;
// Init cache on first run
if (!cache.has(ul)) {
ul.style.setProperty('--tab-width', '');
void ul.offsetHeight;
let max = 0;
tabs.forEach(t => max = Math.max(max, t.offsetWidth));
cache.set(ul, max);
}
// Temporarily use flex wrap to check if wrapping occurs
ul.style.display = 'flex';
ul.style.flexWrap = 'wrap';
void ul.offsetHeight;
const top = tabs[0].offsetTop;
const wrapped = Array.from(tabs).some((t, i) => i > 0 && t.offsetTop !== top);
// Reset display to use CSS grid
ul.style.display = '';
ul.style.flexWrap = '';
// Set CSS variable for wrapped mode
if (wrapped) {
ul.style.setProperty('--tab-width', `${cache.get(ul) + 10}px`);
} else {
ul.style.setProperty('--tab-width', '');
}
return wrapped;
}
function check() {
let any = false;
document.querySelectorAll('ul').forEach(ul => {
if (ul.querySelector('.tab') && checkWrapping(ul)) any = true;
});
document.body.classList.toggle('wrapped-tabs', any);
}
check();
let timer;
window.addEventListener('resize', () => {
clearTimeout(timer);
timer = setTimeout(check, 100);
});
// Re-check wrapping when tabs are switched via anchors
window.addEventListener('hashchange', () => {
clearTimeout(timer);
// Use requestAnimationFrame + setTimeout to ensure DOM has settled
requestAnimationFrame(() => {
timer = setTimeout(check, 0);
});
});
})();
</script>
<script src="{{url_for('static_content', group='js', filename='language-selector.js')}}" defer></script>
<script src="{{url_for('static_content', group='js', filename='search-modal.js')}}" defer></script>
<script src="{{url_for('static_content', group='js', filename='toast.js')}}"></script>

View File

@@ -1,54 +0,0 @@
{# Menu items template - used for both desktop and mobile menus #}
{# CSS media queries handle which version displays - no need for conditional classes #}
{% if current_user.is_authenticated or not has_password %}
{% if not current_diff_url %}
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('tags.') %}active{% endif %}">
<a href="{{ url_for('tags.tags_overview_page') }}" class="pure-menu-link">{{ _('GROUPS') }}</a>
</li>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('settings.') %}active{% endif %}">
<a href="{{ url_for('settings.settings_page') }}" class="pure-menu-link">{{ _('SETTINGS') }}</a>
</li>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('imports.') %}active{% endif %}">
<a href="{{ url_for('imports.import_page') }}" class="pure-menu-link">{{ _('IMPORT') }}</a>
</li>
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('backups.') %}active{% endif %}">
<a href="{{ url_for('backups.index') }}" class="pure-menu-link">{{ _('BACKUPS') }}</a>
</li>
{% else %}
<li class="pure-menu-item menu-collapsible">
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=uuid, next='diff') }}"
class="pure-menu-link">{{ _('EDIT') }}</a>
</li>
{% endif %}
{%- if current_user.is_authenticated -%}
<li class="pure-menu-item menu-collapsible">
<a href="{{ url_for('logout', redirect=request.path) }}" class="pure-menu-link">{{ _('LOG OUT') }}</a>
</li>
{%- endif -%}
{% else %}
<li class="pure-menu-item menu-collapsible">
<a class="pure-menu-link" href="https://changedetection.io">Website Change Detection and Notification.</a>
</li>
{% endif %}
<li class="pure-menu-item menu-collapsible" id="inline-menu-extras-group">
<button class="toggle-button toggle-light-mode " type="button" title="{{ _('Toggle Light/Dark Mode') }}">
<span class="visually-hidden">{{ _('Toggle light/dark mode') }}</span>
<span class="icon-light">
{% include "svgs/light-mode-toggle-icon.svg" %}
</span>
<span class="icon-dark">
{% include "svgs/dark-mode-toggle-icon.svg" %}
</span>
</button>
<button class="toggle-button language-selector" type="button" title="{{ _('Change Language') }}">
<span class="visually-hidden">{{ _('Change language') }}</span>
<span class="{{ get_flag_for_locale(get_locale()) }}" id="language-selector-flag"></span>
</button>
<a class="github-link" href="https://github.com/dgtlmoon/changedetection.io">
{% include "svgs/github.svg" %}
</a>
</li>

View File

@@ -165,83 +165,18 @@ def test_api_simple(client, live_server, measure_memory_usage, datastore_path):
assert b'<div id' in res.data
# Fetch the difference between two versions (default text format)
# Fetch the difference between two versions
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest'),
headers={'x-api-key': api_key},
)
assert b'(changed) Which is across' in res.data
# Test htmlcolor format
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?format=htmlcolor',
headers={'x-api-key': api_key},
)
assert b'aria-label="Changed text" title="Changed text">Which is across multiple lines' in res.data
# Test html format
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?format=html',
headers={'x-api-key': api_key},
)
assert res.status_code == 200
assert b'<br>' in res.data
# Test markdown format
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?format=markdown',
headers={'x-api-key': api_key},
)
assert res.status_code == 200
# Test new diff preference parameters
# Test removed=false (should hide removed content)
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?removed=false',
headers={'x-api-key': api_key},
)
# Should not contain removed content indicator
assert b'(removed)' not in res.data
# Should still contain added content
assert b'(added)' in res.data or b'which has this one new line' in res.data
# Test added=false (should hide added content)
# Note: The test data has replacements, not pure additions, so we test differently
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?added=false&replaced=false',
headers={'x-api-key': api_key},
)
# With both added and replaced disabled, should have minimal content
# Should not contain added indicators
assert b'(added)' not in res.data
# Test replaced=false (should hide replaced/changed content)
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?replaced=false',
headers={'x-api-key': api_key},
)
# Should not contain changed content indicator
assert b'(changed)' not in res.data
# Test type=diffWords for word-level diff
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?type=diffWords&format=htmlcolor',
headers={'x-api-key': api_key},
)
# Should contain HTML formatted diff
assert res.status_code == 200
assert len(res.data) > 0
# Test combined parameters: show only additions with word diff
res = client.get(
url_for("watchhistorydiff", uuid=watch_uuid, from_timestamp='previous', to_timestamp='latest')+'?removed=false&replaced=false&type=diffWords',
headers={'x-api-key': api_key},
)
assert res.status_code == 200
# Should not contain removed or changed markers
assert b'(removed)' not in res.data
assert b'(changed)' not in res.data
# Fetch the whole watch
res = client.get(

View File

@@ -5,7 +5,7 @@ import re
from flask import url_for
from loguru import logger
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks, wait_for_notification_endpoint_output
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks
from . util import extract_UUID_from_client
import logging
import base64
@@ -83,9 +83,7 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
screenshot_dir = os.path.join(datastore_path, str(uuid))
os.makedirs(screenshot_dir, exist_ok=True)
with open(os.path.join(screenshot_dir, 'last-screenshot.png'), 'wb') as f:
with open(os.path.join(datastore_path, str(uuid), 'last-screenshot.png'), 'wb') as f:
f.write(base64.b64decode(testimage_png))
# Goto the edit page, add our ignore text
@@ -144,7 +142,7 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
# Trigger a check
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
wait_for_notification_endpoint_output(datastore_path=datastore_path)
time.sleep(6)
# Check no errors were recorded
res = client.get(url_for("watchlist.index"))
@@ -201,7 +199,7 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
set_more_modified_response(datastore_path=datastore_path)
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
wait_for_notification_endpoint_output(datastore_path=datastore_path)
time.sleep(6)
# Verify what was sent as a notification, this file should exist
with open(os.path.join(datastore_path, "notification.txt"), "r") as f:
notification_submission = f.read()
@@ -242,8 +240,7 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
)
assert b"Updated watch." in res.data
wait_for_all_checks(client)
wait_for_notification_endpoint_output(datastore_path=datastore_path)
time.sleep(2)
# Verify what was sent as a notification, this file should exist
with open(os.path.join(datastore_path, "notification.txt"), "r") as f:
@@ -328,7 +325,7 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server, measure_me
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
wait_for_notification_endpoint_output(datastore_path=datastore_path)
time.sleep(2) # plus extra delay for notifications to fire
# Check no errors were recorded, because we asked for 204 which is slightly uncommon but is still OK
@@ -446,7 +443,7 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage
assert res.status_code != 500
# Give apprise time to fire
wait_for_notification_endpoint_output(datastore_path=datastore_path)
time.sleep(4)
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
x = f.read()
@@ -503,7 +500,7 @@ def test_single_send_test_notification_on_watch(client, live_server, measure_mem
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}&+custom-header=123"
# 1995 UTF-8 content should be encoded
test_body = 'change detection is cool 网站监测 内容更新了 - {{diff_full}}\n\nCurrent snapshot: {{current_snapshot}}'
test_body = 'change detection is cool 网站监测 内容更新了 - {{diff_full}}'
######### Test global/system settings
res = client.post(
url_for("ui.ui_notification.ajax_callback_send_notification_test")+f"/{uuid}",
@@ -528,8 +525,7 @@ def test_single_send_test_notification_on_watch(client, live_server, measure_mem
assert 'title="Changed into">Example text:' not in x
assert 'span' not in x
assert 'Example text:' in x
#3720 current_snapshot check, was working but lets test it exactly.
assert 'Current snapshot: Example text: example test' in x
os.unlink(os.path.join(datastore_path, "notification.txt"))
def _test_color_notifications(client, notification_body_token, datastore_path):
@@ -576,7 +572,7 @@ def _test_color_notifications(client, notification_body_token, datastore_path):
assert b'Queued 1 watch for rechecking.' in res.data
wait_for_all_checks(client)
wait_for_notification_endpoint_output(datastore_path=datastore_path)
time.sleep(2)
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
x = f.read()

View File

@@ -170,7 +170,7 @@ msgstr "Neplatná hodnota."
#: changedetectionio/forms.py:732
msgid "Watch"
msgstr "Monitorovat"
msgstr "# monitory"
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
msgid "Processor"
@@ -178,7 +178,7 @@ msgstr "Procesor"
#: changedetectionio/forms.py:734
msgid "Edit > Watch"
msgstr "Upravit > Monitorovat"
msgstr "Nejprve upravte a poté sledujte"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
msgid "Fetch Method"
@@ -344,7 +344,7 @@ msgstr "Odeslat upozornění, když filtr již na stránce nelze najít"
#: changedetectionio/blueprint/ui/templates/edit.html:59
#: changedetectionio/forms.py:832
msgid "Notifications"
msgstr "Oznámení"
msgstr "Žádné informace"
#: changedetectionio/forms.py:832
msgid "Muted"
@@ -396,7 +396,7 @@ msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
msgid "Name"
msgstr "Název"
msgstr "Zrušit ztlumení"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -420,7 +420,7 @@ msgstr "Požadavky na prostý text"
#: changedetectionio/forms.py:946
msgid "Chrome requests"
msgstr "Chrome požadavky"
msgstr "Žádost"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -476,7 +476,7 @@ msgstr "Kontrola zabezpečení přístupového tokenu API povolena"
#: changedetectionio/forms.py:989
msgid "Notification base URL override"
msgstr "Základní URL pro upozornění"
msgstr "Počet upozornění na upozornění"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -608,11 +608,11 @@ msgstr "No backups found."
#: changedetectionio/blueprint/backups/templates/overview.html:28
msgid "Create backup"
msgstr "Vytvořit zálohu"
msgstr "Create backup"
#: changedetectionio/blueprint/backups/templates/overview.html:30
msgid "Remove backups"
msgstr "Odstranit zálohy"
msgstr "Remove backups"
#: changedetectionio/blueprint/imports/importer.py:45
msgid ""
@@ -823,7 +823,7 @@ msgstr "Generál"
#: changedetectionio/blueprint/settings/templates/settings.html:23
msgid "Fetching"
msgstr "Načítání"
msgstr "Hledání"
#: changedetectionio/blueprint/settings/templates/settings.html:24
msgid "Global Filters"
@@ -851,7 +851,7 @@ msgstr "CAPTCHA a proxy"
#: changedetectionio/blueprint/settings/templates/settings.html:35
msgid "Info"
msgstr "Info"
msgstr "Více informací"
#: changedetectionio/blueprint/settings/templates/settings.html:46
msgid "Default recheck time for all watches, current system minimum is"
@@ -1004,7 +1004,7 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
msgid "# Watches"
msgstr "# monitorů"
msgstr "# monitory"
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
msgid "Tag / Label name"
@@ -1251,7 +1251,7 @@ msgstr "nejprve odkaz."
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
msgid "Confirmation text"
msgstr "Potvrzovací text"
msgstr "Žádné informace"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
msgid "Type in the word"
@@ -1456,7 +1456,7 @@ msgstr "Podmínky"
#: changedetectionio/blueprint/ui/templates/edit.html:60
msgid "Stats"
msgstr "Statistiky"
msgstr "NASTAVENÍ"
#: changedetectionio/blueprint/ui/templates/edit.html:73
#: changedetectionio/blueprint/ui/templates/edit.html:313
@@ -1895,11 +1895,11 @@ msgstr "Přidejte nové monitory zjišťování změn webové stránky"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
msgid "Watch this URL!"
msgstr "Monitorovat tuto URL!"
msgstr "Sledujte tuto adresu URL!"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "Upravit a monitorovat"
msgstr "Nejprve upravte a poté sledujte"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2011,7 +2011,9 @@ msgstr "Změněno"
msgid "No website watches configured, please add a URL in the box above, or"
msgstr ""
"Nejsou nakonfigurována žádná sledování webových stránek, do výše "
"uvedeného pole přidejte adresu URL nebo"
"uvedeného pole přidejte adresu URL neboNejsou nakonfigurována žádná "
"sledování webových stránek, do výše uvedeného pole přidejte adresu URL "
"nebo"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:130
msgid "import a list"
@@ -2048,7 +2050,7 @@ msgstr "Ve frontě"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:250
msgid "History"
msgstr "Historie"
msgstr "Dějiny"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:251
msgid "Preview"
@@ -2399,11 +2401,11 @@ msgstr "Změnit jazyk"
#: changedetectionio/templates/base.html:253
msgid "Watch List"
msgstr "Seznam monitorů"
msgstr "# monitory"
#: changedetectionio/templates/base.html:258
msgid "Watches"
msgstr "Monitory"
msgstr "# monitory"
#: changedetectionio/templates/base.html:261
msgid "Queue Status"

View File

@@ -172,7 +172,7 @@ msgstr "Ungültiger Wert."
#: changedetectionio/forms.py:732
msgid "Watch"
msgstr "Monitor"
msgstr "Watch"
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
msgid "Processor"
@@ -180,7 +180,7 @@ msgstr "Prozessor"
#: changedetectionio/forms.py:734
msgid "Edit > Watch"
msgstr "Bearbeiten > Monitor"
msgstr "Edit > Watch"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
msgid "Fetch Method"
@@ -337,7 +337,7 @@ msgstr "Speichern"
#: changedetectionio/forms.py:829
msgid "Proxy"
msgstr "Proxy"
msgstr "Stellvertreter"
#: changedetectionio/forms.py:831
msgid "Send a notification when the filter can no longer be found on the page"
@@ -350,7 +350,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html:59
#: changedetectionio/forms.py:832
msgid "Notifications"
msgstr "Benachrichtigungen"
msgstr "Keine Informationen"
#: changedetectionio/forms.py:832
msgid "Muted"
@@ -404,7 +404,7 @@ msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
msgid "Name"
msgstr "Name"
msgstr "Stummschaltung aufheben"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -428,7 +428,7 @@ msgstr "Klartextanfragen"
#: changedetectionio/forms.py:946
msgid "Chrome requests"
msgstr "Chrome Requests"
msgstr "Anfrage"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -484,7 +484,7 @@ msgstr "Sicherheitsüberprüfung des API-Zugriffstokens aktiviert"
#: changedetectionio/forms.py:989
msgid "Notification base URL override"
msgstr "Basis-URL für Benachrichtigungen"
msgstr "Anzahl der Benachrichtigungsalarme"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -620,11 +620,11 @@ msgstr "Keine Backups gefunden."
#: changedetectionio/blueprint/backups/templates/overview.html:28
msgid "Create backup"
msgstr "Backup erstellen"
msgstr "BACKUPS"
#: changedetectionio/blueprint/backups/templates/overview.html:30
msgid "Remove backups"
msgstr "Backups entfernen"
msgstr "BACKUPS"
#: changedetectionio/blueprint/imports/importer.py:45
msgid ""
@@ -815,7 +815,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/__init__.py:126
msgid "Settings updated."
msgstr "Einstellungen aktualisiert."
msgstr "EINSTELLUNGEN"
#: changedetectionio/blueprint/settings/__init__.py:129
#: changedetectionio/blueprint/ui/edit.py:283
@@ -839,7 +839,7 @@ msgstr "Allgemein"
#: changedetectionio/blueprint/settings/templates/settings.html:23
msgid "Fetching"
msgstr "Abrufen"
msgstr "Suchen"
#: changedetectionio/blueprint/settings/templates/settings.html:24
msgid "Global Filters"
@@ -867,7 +867,7 @@ msgstr "CAPTCHA & Proxys"
#: changedetectionio/blueprint/settings/templates/settings.html:35
msgid "Info"
msgstr "Info"
msgstr "Weitere Informationen"
#: changedetectionio/blueprint/settings/templates/settings.html:46
msgid "Default recheck time for all watches, current system minimum is"
@@ -897,7 +897,7 @@ msgstr "Keine Plugins aktiv"
#: changedetectionio/blueprint/settings/templates/settings.html:405
msgid "Back"
msgstr "Zurück"
msgstr "BACKUPS"
#: changedetectionio/blueprint/settings/templates/settings.html:406
msgid "Clear Snapshot History"
@@ -942,7 +942,7 @@ msgstr "Filter und Trigger"
#: changedetectionio/blueprint/tags/templates/edit-tag.html:50
msgid "These settings are"
msgstr "Diese Einstellungen sind"
msgstr "EINSTELLUNGEN"
#: changedetectionio/blueprint/tags/templates/edit-tag.html:50
msgid "added"
@@ -1271,7 +1271,7 @@ msgstr "Link zuerst."
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
msgid "Confirmation text"
msgstr "Bestätigungstext"
msgstr "Keine Informationen"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
msgid "Type in the word"
@@ -1420,7 +1420,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/templates/diff.html:127
msgid "from settings."
msgstr "aus den Einstellungen."
msgstr "EINSTELLUNGEN"
#: changedetectionio/blueprint/ui/templates/diff.html:133
msgid "Goto single snapshot"
@@ -1478,7 +1478,7 @@ msgstr "Bedingungen"
#: changedetectionio/blueprint/ui/templates/edit.html:60
msgid "Stats"
msgstr "Statistiken"
msgstr "EINSTELLUNGEN"
#: changedetectionio/blueprint/ui/templates/edit.html:73
#: changedetectionio/blueprint/ui/templates/edit.html:313
@@ -1931,11 +1931,11 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
msgid "Watch this URL!"
msgstr "Diese URL überwachen!"
msgstr "Sehen Sie sich diese URL an!"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "Bearbeiten > Überwachen"
msgstr "Zuerst bearbeiten, dann ansehen"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2396,7 +2396,7 @@ msgstr "EINSTELLUNGEN"
#: changedetectionio/templates/base.html:84
#: changedetectionio/templates/base.html:170
msgid "IMPORT"
msgstr "IMPORTIEREN"
msgstr "IMPORT"
#: changedetectionio/templates/base.html:87
#: changedetectionio/templates/base.html:171

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -181,7 +181,7 @@ msgstr "Processeur"
#: changedetectionio/forms.py:734
msgid "Edit > Watch"
msgstr "Modifier > Surveiller"
msgstr "Modifiez d'abord, puis regardez"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
msgid "Fetch Method"
@@ -342,7 +342,7 @@ msgstr "Sauvegarder"
#: changedetectionio/forms.py:829
msgid "Proxy"
msgstr "Proxy"
msgstr "Procuration"
#: changedetectionio/forms.py:831
msgid "Send a notification when the filter can no longer be found on the page"
@@ -353,7 +353,7 @@ msgstr "Envoyer une notification lorsque le filtre n'est plus trouvé sur la pag
#: changedetectionio/blueprint/ui/templates/edit.html:59
#: changedetectionio/forms.py:832
msgid "Notifications"
msgstr "Notifications"
msgstr "Aucune information"
#: changedetectionio/forms.py:832
msgid "Muted"
@@ -405,7 +405,7 @@ msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
msgid "Name"
msgstr "Nom"
msgstr "Réactiver le son"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -429,7 +429,7 @@ msgstr "Requêtes en texte brut"
#: changedetectionio/forms.py:946
msgid "Chrome requests"
msgstr "Requêtes Chrome"
msgstr "Demande"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -485,7 +485,7 @@ msgstr "Contrôle de sécurité du jeton d'accès à l'API activé"
#: changedetectionio/forms.py:989
msgid "Notification base URL override"
msgstr "URL de base pour les notifications"
msgstr "Nombre d'alertes de notification"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -621,11 +621,11 @@ msgstr "Aucune sauvegarde trouvée."
#: changedetectionio/blueprint/backups/templates/overview.html:28
msgid "Create backup"
msgstr "Créer sauvegarde"
msgstr "SAUVEGARDES"
#: changedetectionio/blueprint/backups/templates/overview.html:30
msgid "Remove backups"
msgstr "Supprimer sauvegardes"
msgstr "SAUVEGARDES"
#: changedetectionio/blueprint/imports/importer.py:45
msgid ""
@@ -844,7 +844,7 @@ msgstr "Général"
#: changedetectionio/blueprint/settings/templates/settings.html:23
msgid "Fetching"
msgstr "Récupération"
msgstr "Recherche"
#: changedetectionio/blueprint/settings/templates/settings.html:24
msgid "Global Filters"
@@ -872,7 +872,7 @@ msgstr "CAPTCHA et procurations"
#: changedetectionio/blueprint/settings/templates/settings.html:35
msgid "Info"
msgstr "Info"
msgstr "Plus d'informations"
#: changedetectionio/blueprint/settings/templates/settings.html:46
msgid "Default recheck time for all watches, current system minimum is"
@@ -902,7 +902,7 @@ msgstr "Aucun plugin actif"
#: changedetectionio/blueprint/settings/templates/settings.html:405
msgid "Back"
msgstr "Retour"
msgstr "SAUVEGARDES"
#: changedetectionio/blueprint/settings/templates/settings.html:406
msgid "Clear Snapshot History"
@@ -1276,7 +1276,7 @@ msgstr "lien d'abord."
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
msgid "Confirmation text"
msgstr "Texte de confirmation"
msgstr "Aucune information"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
msgid "Type in the word"
@@ -1483,7 +1483,7 @@ msgstr "Conditions"
#: changedetectionio/blueprint/ui/templates/edit.html:60
msgid "Stats"
msgstr "Statistiques"
msgstr "PARAMÈTRES"
#: changedetectionio/blueprint/ui/templates/edit.html:73
#: changedetectionio/blueprint/ui/templates/edit.html:313
@@ -1942,7 +1942,7 @@ msgstr "Surveillez cette URL !"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "Modifier > Surveiller"
msgstr "Modifiez d'abord, puis regardez"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2054,7 +2054,8 @@ msgstr "Modifié"
msgid "No website watches configured, please add a URL in the box above, or"
msgstr ""
"Aucune surveillance de site Web configurée, veuillez ajouter une URL dans"
" la case ci-dessus, ou"
" la case ci-dessus, ouAucune surveillance de site Web configurée, "
"veuillez ajouter une URL dans la case ci-dessus, ou"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:130
msgid "import a list"
@@ -2091,7 +2092,7 @@ msgstr "En file d'attente"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:250
msgid "History"
msgstr "Historique"
msgstr "Histoire"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:251
msgid "Preview"

View File

@@ -175,15 +175,16 @@ msgstr "Valore non valido."
#: changedetectionio/forms.py:732
msgid "Watch"
msgstr "Monitora"
msgstr "Osserva"
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
msgid "Processor"
msgstr "Processore"
#: changedetectionio/forms.py:734
#, fuzzy
msgid "Edit > Watch"
msgstr "Modifica > Monitora"
msgstr "Modifica prima poi Monitora"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
msgid "Fetch Method"
@@ -414,8 +415,9 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
#, fuzzy
msgid "Name"
msgstr "Nome"
msgstr "Riattiva audio"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -438,8 +440,9 @@ msgid "Plaintext requests"
msgstr "Richieste in chiaro"
#: changedetectionio/forms.py:946
#, fuzzy
msgid "Chrome requests"
msgstr "Richieste Chrome"
msgstr "Richiesta"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -494,8 +497,9 @@ msgid "API access token security check enabled"
msgstr "Controllo sicurezza token API attivo"
#: changedetectionio/forms.py:989
#, fuzzy
msgid "Notification base URL override"
msgstr "URL base notifiche"
msgstr "Notifiche"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -1022,7 +1026,7 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
msgid "# Watches"
msgstr "# Monitoraggi"
msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
msgid "Tag / Label name"
@@ -1269,7 +1273,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
msgid "Confirmation text"
msgstr "Testo di conferma"
msgstr ""
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
msgid "Type in the word"
@@ -1911,7 +1915,7 @@ msgstr "Monitora questo URL!"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "Modifica > Monitora"
msgstr "Modifica prima poi Monitora"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2021,7 +2025,7 @@ msgstr "Modifica"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:130
msgid "No website watches configured, please add a URL in the box above, or"
msgstr "Nessun monitoraggio configurato, aggiungi un URL nella casella sopra, oppure"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:130
msgid "import a list"
@@ -2424,12 +2428,12 @@ msgstr "Cambia lingua"
#: changedetectionio/templates/base.html:253
#, fuzzy
msgid "Watch List"
msgstr "Lista Monitoraggi"
msgstr "Osserva"
#: changedetectionio/templates/base.html:258
#, fuzzy
msgid "Watches"
msgstr "Monitoraggi"
msgstr "Osserva"
#: changedetectionio/templates/base.html:261
msgid "Queue Status"

View File

@@ -176,16 +176,18 @@ msgid "Invalid value."
msgstr "값이 잘못되었습니다."
#: changedetectionio/forms.py:732
#, fuzzy
msgid "Watch"
msgstr "모니터"
msgstr "# 시계"
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
msgid "Processor"
msgstr "프로세서"
#: changedetectionio/forms.py:734
#, fuzzy
msgid "Edit > Watch"
msgstr "편집 > 모니터"
msgstr "먼저 편집한 다음 보기"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
#, fuzzy
@@ -361,7 +363,7 @@ msgstr "구하다"
#: changedetectionio/forms.py:829
msgid "Proxy"
msgstr "프록시"
msgstr "대리"
#: changedetectionio/forms.py:831
msgid "Send a notification when the filter can no longer be found on the page"
@@ -372,7 +374,7 @@ msgstr "페이지에서 필터를 더 이상 찾을 수 없으면 알림 보내
#: changedetectionio/blueprint/ui/templates/edit.html:59
#: changedetectionio/forms.py:832
msgid "Notifications"
msgstr "알림"
msgstr "정보 없음"
#: changedetectionio/forms.py:832
#, fuzzy
@@ -425,8 +427,9 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
#, fuzzy
msgid "Name"
msgstr "이름"
msgstr "음소거 해제"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -449,8 +452,9 @@ msgid "Plaintext requests"
msgstr "일반 텍스트 요청"
#: changedetectionio/forms.py:946
#, fuzzy
msgid "Chrome requests"
msgstr "Chrome 요청"
msgstr "요구"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -507,8 +511,9 @@ msgid "API access token security check enabled"
msgstr "API 액세스 토큰 보안 확인이 활성화되었습니다."
#: changedetectionio/forms.py:989
#, fuzzy
msgid "Notification base URL override"
msgstr "알림 기본 URL"
msgstr "알림 경고 수"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -642,11 +647,11 @@ msgstr "백업을 찾을 수 없습니다."
#: changedetectionio/blueprint/backups/templates/overview.html:28
msgid "Create backup"
msgstr "백업 생성"
msgstr "백업"
#: changedetectionio/blueprint/backups/templates/overview.html:30
msgid "Remove backups"
msgstr "백업 삭제"
msgstr "백업"
#: changedetectionio/blueprint/imports/importer.py:45
msgid ""
@@ -854,7 +859,7 @@ msgstr "일반적인"
#: changedetectionio/blueprint/settings/templates/settings.html:23
msgid "Fetching"
msgstr "가져오기"
msgstr "수색"
#: changedetectionio/blueprint/settings/templates/settings.html:24
msgid "Global Filters"
@@ -882,7 +887,7 @@ msgstr "보안 문자 및 프록시"
#: changedetectionio/blueprint/settings/templates/settings.html:35
msgid "Info"
msgstr "정보"
msgstr "추가 정보"
#: changedetectionio/blueprint/settings/templates/settings.html:46
msgid "Default recheck time for all watches, current system minimum is"
@@ -910,7 +915,7 @@ msgstr "활성화된 플러그인이 없습니다."
#: changedetectionio/blueprint/settings/templates/settings.html:405
msgid "Back"
msgstr "뒤로"
msgstr "백업"
#: changedetectionio/blueprint/settings/templates/settings.html:406
msgid "Clear Snapshot History"
@@ -1033,7 +1038,7 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
msgid "# Watches"
msgstr "# 모니터"
msgstr "# 시계"
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
msgid "Tag / Label name"
@@ -1285,7 +1290,7 @@ msgstr "먼저 링크하세요."
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
msgid "Confirmation text"
msgstr "확인 텍스트"
msgstr "정보 없음"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
msgid "Type in the word"
@@ -1488,7 +1493,7 @@ msgstr "정황"
#: changedetectionio/blueprint/ui/templates/edit.html:60
msgid "Stats"
msgstr "통계"
msgstr "설정"
#: changedetectionio/blueprint/ui/templates/edit.html:73
#: changedetectionio/blueprint/ui/templates/edit.html:313
@@ -1923,11 +1928,11 @@ msgstr "새로운 웹 페이지 변경 감지 감시 추가"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
msgid "Watch this URL!"
msgstr "이 URL 모니터!"
msgstr "이 URL을 시청하세요!"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "편집 후 모니터"
msgstr "먼저 편집한 다음 보기"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2074,7 +2079,7 @@ msgstr "대기 중"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:250
msgid "History"
msgstr "기록"
msgstr "역사"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:251
msgid "Preview"
@@ -2400,7 +2405,7 @@ msgstr "설정"
#: changedetectionio/templates/base.html:84
#: changedetectionio/templates/base.html:170
msgid "IMPORT"
msgstr "가져오기"
msgstr "수입"
#: changedetectionio/templates/base.html:87
#: changedetectionio/templates/base.html:171
@@ -2440,12 +2445,12 @@ msgstr "언어 변경"
#: changedetectionio/templates/base.html:253
#, fuzzy
msgid "Watch List"
msgstr "모니터 목록"
msgstr "# 시계"
#: changedetectionio/templates/base.html:258
#, fuzzy
msgid "Watches"
msgstr "모니터"
msgstr "# 시계"
#: changedetectionio/templates/base.html:261
msgid "Queue Status"

View File

@@ -176,8 +176,9 @@ msgid "Invalid value."
msgstr "无效值。"
#: changedetectionio/forms.py:732
#, fuzzy
msgid "Watch"
msgstr "监控"
msgstr "# 手表"
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
msgid "Processor"
@@ -186,7 +187,7 @@ msgstr "处理器"
#: changedetectionio/forms.py:734
#, fuzzy
msgid "Edit > Watch"
msgstr "编辑 > 监控"
msgstr "编辑后观看"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
#, fuzzy
@@ -362,7 +363,7 @@ msgstr "节省"
#: changedetectionio/forms.py:829
msgid "Proxy"
msgstr "代理"
msgstr "代理"
#: changedetectionio/forms.py:831
msgid "Send a notification when the filter can no longer be found on the page"
@@ -373,7 +374,7 @@ msgstr "当页面上找不到过滤器时发送通知"
#: changedetectionio/blueprint/ui/templates/edit.html:59
#: changedetectionio/forms.py:832
msgid "Notifications"
msgstr "通知"
msgstr "暂无信息"
#: changedetectionio/forms.py:832
#, fuzzy
@@ -426,8 +427,9 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
#, fuzzy
msgid "Name"
msgstr "名称"
msgstr "取消静音"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -450,8 +452,9 @@ msgid "Plaintext requests"
msgstr "明文请求"
#: changedetectionio/forms.py:946
#, fuzzy
msgid "Chrome requests"
msgstr "Chrome请求"
msgstr "求"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -508,8 +511,9 @@ msgid "API access token security check enabled"
msgstr "已启用 API 访问令牌安全检查"
#: changedetectionio/forms.py:989
#, fuzzy
msgid "Notification base URL override"
msgstr "通知基础URL"
msgstr "通知警报计数"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -643,11 +647,11 @@ msgstr "未找到备份。"
#: changedetectionio/blueprint/backups/templates/overview.html:28
msgid "Create backup"
msgstr "创建备份"
msgstr "备份"
#: changedetectionio/blueprint/backups/templates/overview.html:30
msgid "Remove backups"
msgstr "删除备份"
msgstr "备份"
#: changedetectionio/blueprint/imports/importer.py:45
msgid ""
@@ -855,7 +859,7 @@ msgstr "一般的"
#: changedetectionio/blueprint/settings/templates/settings.html:23
msgid "Fetching"
msgstr "获取"
msgstr "搜寻中"
#: changedetectionio/blueprint/settings/templates/settings.html:24
msgid "Global Filters"
@@ -883,7 +887,7 @@ msgstr "验证码和代理"
#: changedetectionio/blueprint/settings/templates/settings.html:35
msgid "Info"
msgstr "信息"
msgstr "更多信息"
#: changedetectionio/blueprint/settings/templates/settings.html:46
msgid "Default recheck time for all watches, current system minimum is"
@@ -911,7 +915,7 @@ msgstr "没有激活的插件"
#: changedetectionio/blueprint/settings/templates/settings.html:405
msgid "Back"
msgstr "返回"
msgstr "备份"
#: changedetectionio/blueprint/settings/templates/settings.html:406
msgid "Clear Snapshot History"
@@ -1034,7 +1038,7 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
msgid "# Watches"
msgstr "# 监控项"
msgstr "# 手表"
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
msgid "Tag / Label name"
@@ -1286,7 +1290,7 @@ msgstr "先链接。"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
msgid "Confirmation text"
msgstr "确认文本"
msgstr "暂无信息"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
msgid "Type in the word"
@@ -1489,7 +1493,7 @@ msgstr "状况"
#: changedetectionio/blueprint/ui/templates/edit.html:60
msgid "Stats"
msgstr "统计"
msgstr "设置"
#: changedetectionio/blueprint/ui/templates/edit.html:73
#: changedetectionio/blueprint/ui/templates/edit.html:313
@@ -1924,11 +1928,11 @@ msgstr "添加新的网页更改检测监视"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
msgid "Watch this URL!"
msgstr "监控此URL"
msgstr "关注这个网址"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "编辑后监控"
msgstr "编辑后观看"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2401,7 +2405,7 @@ msgstr "设置"
#: changedetectionio/templates/base.html:84
#: changedetectionio/templates/base.html:170
msgid "IMPORT"
msgstr "导入"
msgstr "进口"
#: changedetectionio/templates/base.html:87
#: changedetectionio/templates/base.html:171
@@ -2441,12 +2445,12 @@ msgstr "更改语言"
#: changedetectionio/templates/base.html:253
#, fuzzy
msgid "Watch List"
msgstr "监控列表"
msgstr "# 手表"
#: changedetectionio/templates/base.html:258
#, fuzzy
msgid "Watches"
msgstr "监控项"
msgstr "# 手表"
#: changedetectionio/templates/base.html:261
msgid "Queue Status"

View File

@@ -176,16 +176,18 @@ msgid "Invalid value."
msgstr "無效值。"
#: changedetectionio/forms.py:732
#, fuzzy
msgid "Watch"
msgstr "監控"
msgstr "# 手錶"
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
msgid "Processor"
msgstr "處理器"
#: changedetectionio/forms.py:734
#, fuzzy
msgid "Edit > Watch"
msgstr "編輯 > 監控"
msgstr "編輯後觀看"
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
#, fuzzy
@@ -361,7 +363,7 @@ msgstr "節省"
#: changedetectionio/forms.py:829
msgid "Proxy"
msgstr "代理"
msgstr "代理"
#: changedetectionio/forms.py:831
msgid "Send a notification when the filter can no longer be found on the page"
@@ -425,8 +427,9 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
msgstr ""
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
#, fuzzy
msgid "Name"
msgstr "名稱"
msgstr "取消靜音"
#: changedetectionio/forms.py:921
msgid "Proxy URL"
@@ -449,8 +452,9 @@ msgid "Plaintext requests"
msgstr "明文請求"
#: changedetectionio/forms.py:946
#, fuzzy
msgid "Chrome requests"
msgstr "Chrome請求"
msgstr "求"
#: changedetectionio/forms.py:952
msgid "Default proxy"
@@ -507,8 +511,9 @@ msgid "API access token security check enabled"
msgstr "已啟用 API 訪問令牌安全檢查"
#: changedetectionio/forms.py:989
#, fuzzy
msgid "Notification base URL override"
msgstr "通知基礎URL"
msgstr "通知警報計數"
#: changedetectionio/forms.py:993
msgid "Treat empty pages as a change?"
@@ -1033,7 +1038,7 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
msgid "# Watches"
msgstr "# 監控項"
msgstr "# 手錶"
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
msgid "Tag / Label name"
@@ -1923,11 +1928,11 @@ msgstr "添加新的網頁更改檢測監視"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
msgid "Watch this URL!"
msgstr "監控此URL"
msgstr "關注這個網址"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
msgid "Edit first then Watch"
msgstr "編輯後監控"
msgstr "編輯後觀看"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
msgid "Create a shareable link"
@@ -2400,7 +2405,7 @@ msgstr "設定"
#: changedetectionio/templates/base.html:84
#: changedetectionio/templates/base.html:170
msgid "IMPORT"
msgstr "導入"
msgstr "進口"
#: changedetectionio/templates/base.html:87
#: changedetectionio/templates/base.html:171
@@ -2440,12 +2445,12 @@ msgstr "更改語言"
#: changedetectionio/templates/base.html:253
#, fuzzy
msgid "Watch List"
msgstr "監控列表"
msgstr "# 手錶"
#: changedetectionio/templates/base.html:258
#, fuzzy
msgid "Watches"
msgstr "監控項"
msgstr "# 手錶"
#: changedetectionio/templates/base.html:261
msgid "Queue Status"

View File

@@ -28,7 +28,7 @@ info:
For example: `x-api-key: YOUR_API_KEY`
version: 0.1.4
version: 0.1.3
contact:
name: ChangeDetection.io
url: https://github.com/dgtlmoon/changedetection.io
@@ -761,9 +761,9 @@ paths:
get:
operationId: getWatchHistoryDiff
tags: [Watch History]
summary: Get the difference between two snapshots
summary: Get diff between two snapshots
description: |
Generate a difference (comparison) between two historical snapshots of a web page change monitor (watch).
Generate a formatted diff (comparison) between two historical snapshots of a web page change monitor (watch).
This endpoint compares content between two points in time and returns the differences in your chosen format.
Perfect for reviewing what changed between specific versions or comparing recent changes.
@@ -798,10 +798,6 @@ paths:
# Compare two specific timestamps in plain text with word-level diff
curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/difference/1640995200/1640998800?format=text&word_diff=true" \
-H "x-api-key: YOUR_API_KEY"
# Show only additions (hide removed/replaced content), ignore whitespace
curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/difference/previous/latest?format=htmlcolor&removed=false&replaced=false&ignoreWhitespace=true" \
-H "x-api-key: YOUR_API_KEY"
- lang: 'Python'
source: |
import requests
@@ -826,20 +822,6 @@ paths:
params={'format': 'text', 'word_diff': 'true'}
)
print(response.text)
# Show only additions, ignore whitespace and use word-level diff
response = requests.get(
f'http://localhost:5000/api/v1/watch/{uuid}/difference/previous/latest',
headers=headers,
params={
'format': 'htmlcolor',
'type': 'diffWords',
'removed': 'false',
'replaced': 'false',
'ignoreWhitespace': 'true'
}
)
print(response.text)
parameters:
- name: uuid
in: path
@@ -879,10 +861,9 @@ paths:
- `text` (default): Plain text with (removed) and (added) prefixes
- `html`: Basic HTML format
- `htmlcolor`: Rich HTML with colored backgrounds (red for deletions, green for additions)
- `markdown`: Markdown format with HTML rendering
schema:
type: string
enum: [text, html, htmlcolor, markdown]
enum: [text, html, htmlcolor]
default: text
- name: word_diff
in: query
@@ -907,69 +888,6 @@ paths:
type: string
enum: ["true", "false", "1", "0", "yes", "no", "on", "off"]
default: "false"
- name: type
in: query
description: |
Diff granularity type:
- `diffLines` (default): Line-level comparison, showing which lines changed
- `diffWords`: Word-level comparison, showing which words changed within lines
This parameter is an alternative to `word_diff` for better alignment with the UI.
If both are specified, `type=diffWords` will enable word-level diffing.
schema:
type: string
enum: [diffLines, diffWords]
default: diffLines
- name: changesOnly
in: query
description: |
When enabled, only show lines/content that changed (no surrounding context).
When disabled, include unchanged lines for context around changes.
Accepts: true, false, 1, 0, yes, no, on, off
schema:
type: string
enum: ["true", "false", "1", "0", "yes", "no", "on", "off"]
default: "true"
- name: ignoreWhitespace
in: query
description: |
When enabled, ignore whitespace-only changes (spaces, tabs, newlines).
Useful for focusing on content changes and ignoring formatting differences.
Accepts: true, false, 1, 0, yes, no, on, off
schema:
type: string
enum: ["true", "false", "1", "0", "yes", "no", "on", "off"]
default: "false"
- name: removed
in: query
description: |
Include removed/deleted content in the diff output.
When disabled, content that was deleted will not appear in the diff.
Accepts: true, false, 1, 0, yes, no, on, off
schema:
type: string
enum: ["true", "false", "1", "0", "yes", "no", "on", "off"]
default: "true"
- name: added
in: query
description: |
Include added/new content in the diff output.
When disabled, content that was added will not appear in the diff.
Accepts: true, false, 1, 0, yes, no, on, off
schema:
type: string
enum: ["true", "false", "1", "0", "yes", "no", "on", "off"]
default: "true"
- name: replaced
in: query
description: |
Include replaced/modified content in the diff output.
When disabled, content that was modified (changed from one value to another) will not appear in the diff.
Accepts: true, false, 1, 0, yes, no, on, off
schema:
type: string
enum: ["true", "false", "1", "0", "yes", "no", "on", "off"]
default: "true"
responses:
'200':
description: Formatted diff between the two snapshots

File diff suppressed because one or more lines are too long

View File

@@ -12,8 +12,8 @@ janus # Thread-safe async/sync queue bridge
flask_wtf~=1.2
flask~=3.1
flask-socketio~=5.6.0
python-socketio~=5.16.0
python-engineio~=4.13.0
python-socketio~=5.14.3
python-engineio~=4.12.3
inscriptis~=2.2
pytz
timeago~=1.0
@@ -60,7 +60,7 @@ cryptography==46.0.3
paho-mqtt!=2.0.*
# Used for CSS filtering, JSON extraction from HTML
beautifulsoup4>=4.0.0,<=4.14.3
beautifulsoup4>=4.0.0,<=4.14.2
# XPath filtering, lxml is required by bs4 anyway, but put it here to be safe.
# #2328 - 5.2.0 and 5.2.1 had extra CPU flag CFLAGS set which was not compatible on older hardware
@@ -148,7 +148,7 @@ tzdata
pluggy ~= 1.6
# Needed for testing, cross-platform for process and system monitoring
psutil==7.2.1
psutil==7.1.0
ruff >= 0.11.2
pre_commit >= 4.2.0