Compare commits

...
Author SHA1 Message Date
dgtlmoon 26110850b7 rebuild 2026-09-09 16:37:07 +02:00
dgtlmoon 7c1e9ce8df Tidy up strings 2026-09-09 16:36:11 +02:00
dgtlmoon ad2ce6e865 UI - Edit watch - Moving notification error alert to 'error' tab, hiding less used Page Title and Link options under foldout 2026-09-09 16:28:57 +02:00
dgtlmoonandClaude Opus 5 821b538ae6 Performance - (watched page lister) open link single eval (#4405)
* Watchlist/Notifications - Resolve 'Link to Open' once per row, don't leak 'DISABLED' into notification tokens

Follow-up to #4290.

`watch.open_link` was read three times per watch list row (two hrefs plus the
new title attribute). Each read re-runs `_resolve_link()`, which for a Jinja2
templated URL builds a fresh ImmutableSandboxedEnvironment - the cost the
comment in validate_url.py already warns about. Hoisted to a single
`{% set open_link = watch.open_link %}` alongside the other per-row lookups.

`watch_open_url` was set from `watch.open_link`, which returns the string
'DISABLED' when the URL fails validation, so the default RSS body template
(`RSS_TEMPLATE_HTML_DEFAULT`) rendered `<a href="DISABLED">` for those watches.
Now falls back to the raw URL, matching the neighbouring `watch_url` token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Rebuild template

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:19:34 +02:00
dgtlmoon c104179ab5 Rebuild translations 2026-09-09 16:13:13 +02:00
Jeff Hedlund 2f60135cd0 feature: Implemented optional override link (#4290)
* Implemented optional link to override feature

* Added translation strings (untranslated)
2026-09-09 16:06:24 +02:00
dgtlmoon f6d710ca85 Apprise notifications - Updating to 1.13.1 (#4404) 2026-09-09 15:50:58 +02:00
dgtlmoon 7f3645f4a8 API - DELETE for Watch history #4397 (#4403) 2026-09-09 15:46:00 +02:00
455e0228ca Tests - Fix TestHistoryPathTraversal on macOS, and cover the containment check (#4390)
* Compare against a resolved data_dir in the history path-traversal test

Watch.history resolves entries with os.path.realpath, so
test_normal_snapshot_entry_is_accepted compared a resolved path against an
unresolved data_dir. On macOS the datastore lives under /tmp, which is a
symlink to /private/tmp, so the assertion fails for a path that is in fact
inside the directory. The guard is correct; the test was not.

Resolve both sides, matching what the production code does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Add a test that actually exercises the history containment check

Disabling the containment check in Watch.history left every test in
TestHistoryPathTraversal passing. os.path.basename() reduces both traversal
fixtures ('/etc/passwd', '../../etc/passwd') to 'passwd', so neither reaches
the check — they stop at the os.path.exists() test below it.

A bare '..' survives basename() and resolves to the parent of data_dir, which
exists, so the containment check is what rejects it. With that check disabled
this new test is the only one in the class that fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: GG5533 <285285461+GG5533@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 15:43:54 +02:00
dgtlmoon cd15016465 UI - Language selector menu-bar icon was cut off by a couple of pixels 2026-09-09 15:16:47 +02:00
dgtlmoon d6996b7a7e Translations - Rebuild catalog 2026-09-09 15:13:59 +02:00
dgtlmoon f1c681185f UI - 'Toast' notifications of actions was at the same height as menu tabs, moved down slightly. 2026-09-09 15:12:28 +02:00
dgtlmoon d901efc8e7 UI - Left menu folds out ONTOP of content, Improving VisualSelector & BrowserSteps width, Darkmode transitions off, Extra sidebar modes #4380 (#4401) 2026-09-09 15:09:27 +02:00
Hans Valerie 1dc7850d6c i18n: add Indonesian translation (#4402) 2026-09-09 15:08:40 +02:00
60 changed files with 6396 additions and 321 deletions
+16
View File
@@ -141,6 +141,7 @@ class Watch(Resource):
watch['last_changed'] = watch_obj.last_changed
watch['viewed'] = watch_obj.viewed
watch['link'] = watch_obj.link
watch['open_link'] = watch_obj.open_link
# Resolved processor config: tag override wins over watch-level config (mirrors restock processor logic)
import json
@@ -293,6 +294,20 @@ class WatchHistory(Resource):
abort(404, message='No watch exists with the UUID of {}'.format(uuid))
return watch.history, 200
# Delete all history/snapshots for a watch, but keep the watch itself
# curl -X DELETE http://localhost:5000/api/v1/watch/<uuid_str:uuid>/history
@auth.check_token
@validate_openapi_request('deleteWatchHistory')
def delete(self, uuid):
"""Clear all snapshot history for a watch (the watch itself is kept)."""
if not self.datastore.data['watching'].get(uuid):
abort(404, message='No watch exists with the UUID of {}'.format(uuid))
# Same call as the UI "Clear history" button - wipes snapshots/screenshots and
# resets last_checked etc, while preserving the watch and its processor config
self.datastore.clear_watch_history(uuid)
return 'OK', 204
class WatchSingleHistory(Resource):
def __init__(self, **kwargs):
@@ -596,6 +611,7 @@ class CreateWatch(Resource):
'last_checked': watch['last_checked'],
'last_error': watch['last_error'],
'link': watch.link,
'open_link': watch.open_link,
'page_title': watch['page_title'],
'tags': [*tags], # Unpack dict keys to list (can't use list() since variable named 'list')
'title': watch['title'],
+16
View File
@@ -0,0 +1,16 @@
"""Left-rail (action sidebar) display modes.
A leaf module on purpose: forms.py, flask_app.py and model/App.py all need these, and
model/App.py seeding its default from forms.py would drag the whole form stack (~550
modules) into the model layer.
"""
from flask_babel import lazy_gettext as _l
# The complete set of left-rail modes - flask_app.get_sidebar_mode_class() maps these
# (and only these) onto body classes, so a new mode here needs a new mapping there.
MENU_SIDEBAR_ACTIONMODES = [
('expandable', _l('Expand on hover')), # Slim icon rail that expands on hover/focus
('pinned-expanded', _l('Always expanded')), # Always expanded, never collapses
('minimal', _l('Stays minimal')), # Always small, never expands
]
MENU_SIDEBAR_ACTIONMODES_DEFAULT = 'expandable'
+1 -1
View File
@@ -24,4 +24,4 @@ RSS_TEMPLATE_PLAINTEXT_DEFAULT = "<pre>{{watch_label}} had a change.\n\n{{diff}}
# @todo add some [edit]/[history]/[goto] etc links
# @todo need {{watch_edit_link}} + delete + history link token
RSS_TEMPLATE_HTML_DEFAULT = "<html><body>\n<h4><a href=\"{{watch_url}}\">{{watch_label}}</a></h4>\n<p>{{diff}}</p>\n</body></html>\n"
RSS_TEMPLATE_HTML_DEFAULT = "<html><body>\n<h4><a href=\"{{watch_open_url}}\">{{watch_label}}</a></h4>\n<p>{{diff}}</p>\n</body></html>\n"
@@ -105,7 +105,7 @@ def construct_single_watch_routes(rss_blueprint, datastore):
fe = fg.add_entry()
title_suffix = f"Change @ {res['original_context']['change_datetime']}"
populate_feed_entry(fe, watch, res.get('body', ''), guid, timestamp_to,
link={'href': watch.get('url')}, title_suffix=title_suffix)
link={'href': watch.open_link_override or watch.get('url')}, title_suffix=title_suffix)
add_watch_categories(fe, watch, datastore)
response = make_response(fg.rss_str())
@@ -70,6 +70,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
# use the same as when it is triggered, but then override it with the form test values
n_object = NotificationContextData({
'watch_url': request.form.get('window_url', "https://changedetection.io"),
# Falls back to watch_url when the watch has no 'Link to Open' set
'watch_open_url': watch.open_link_override or request.form.get('window_url', "https://changedetection.io"),
'notification_urls': notification_urls
})
+1 -1
View File
@@ -104,7 +104,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
output = render_template("preview.html",
capabilities=capabilities,
content=content,
current_diff_url=watch['url'],
current_diff_url=watch.open_link,
current_version=timestamp,
extra_stylesheets=extra_stylesheets,
extra_title=f" - {gettext('Diff')} - {watch.label} @ {timestamp}",
@@ -8,7 +8,7 @@
{% endif %}
const highlight_submit_ignore_url="{{url_for('ui.ui_edit.highlight_submit_ignore_url', uuid=uuid)}}";
const watch_url= {{watch_a.link|tojson}};
const watch_url= {{watch_a.open_link|tojson}};
// Initial scroll position: if set, scroll to this line number in #difference on page load
const initialScrollToLineNumber = {{ initial_scroll_line_number|default('null') }};
@@ -100,6 +100,23 @@
{{ render_field(form.title, class="m-d", placeholder=watch.label) }}
<span class="pure-form-message-inline">{{ _('Automatically uses the page title if found, you can also use your own title/description here') }}</span>
</div>
<div class="pure-control-group">
<details class="form-wrapper">
<summary>{{ _('Extra Page Title and Link options') }}</summary>
<br>
<div class=" border-fieldset">
<div class="pure-control-group">
{{ render_field(form.link_to_open, placeholder="https://...", class="m-d") }}
<span class="pure-form-message-inline">{{ _('Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked.') }}</span>
</div>
<div class="pure-control-group">
{{ render_ternary_field(form.use_page_title_in_list) }}
</div>
</div>
</details>
</div>
<div class="pure-control-group time-between-check border-fieldset">
{{ render_checkbox_field(form.time_between_check_use_default, class="use-default-timecheck") }}
@@ -119,13 +136,6 @@
</div>
<br>
</div>
<div class="pure-control-group">
{{ render_checkbox_field(form.filter_failure_notification_send) }}
<span class="pure-form-message-inline">
{{ _('Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and your filter will not work anymore.') }}
</span>
</div>
<div class="pure-control-group">
{{ render_field(form.history_snapshot_max_length, class="history_snapshot_max_length") }}
<span class="pure-form-message-inline">{{ _('Limit collection of history snapshots for each watch to this number of history items.') }}
@@ -133,9 +143,7 @@
{{ _('Set to empty to use system settings default') }}
</span>
</div>
<div class="pure-control-group">
{{ render_ternary_field(form.use_page_title_in_list) }}
</div>
</fieldset>
</div>
@@ -250,7 +258,7 @@ Math: {{ 1 + 1 }}") }}
<div class="flex-wrapper" >
<div id="browser-steps-ui" class="noselect">
<div class="noselect" id="browsersteps-selector-wrapper" style="width: 100%">
<div class="noselect" id="browsersteps-selector-wrapper">
<span class="loader" >
<span id="browsersteps-click-start">
<h2 >{{ _('Click here to Start') }}</h2>
@@ -295,6 +303,12 @@ Math: {{ 1 + 1 }}") }}
</span>
</div>
{% endif %}
<div class="pure-control-group">
{{ render_checkbox_field(form.filter_failure_notification_send) }}
<span class="pure-form-message-inline">
{{ _('Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and your filter will not work anymore.') }}
</span>
</div>
<div class="field-group" id="notification-field-group">
{% if has_default_notification_urls %}
<div class="inline-warning">
@@ -11,6 +11,9 @@
{%- set checking_now = is_checking_now(watch) -%}
{%- set history_n = watch.history_n -%}
{%- set favicon = watch.get_favicon_filename() -%}
{# Resolved once per row: each read re-validates the URL and, for a Jinja2 templated
URL, builds a fresh sandboxed environment - see validate_url.py #}
{%- set open_link = watch.open_link -%}
{%- set error_texts = watch.compile_error_texts(has_proxies=has_proxies) -%}
{%- set system_use_url_watchlist = datastore.data['settings']['application']['ui'].get('use_page_title_in_list') -%}
{# Class settings mirrored in changedetectionio/static/js/realtime.js for the frontend #}
@@ -48,7 +51,7 @@
<div class="grid-wrapper">
{% if 'favicons_enabled' not in ui_settings or ui_settings['favicons_enabled'] %}
<div class="favicon">
<a target="_blank" rel="noopener" href="{{ watch.link.replace('source:','') }}">
<a target="_blank" rel="noopener" href="{{ open_link }}">
{# Intersection Observer lazy loading: store real URL in data-src, load only when visible in viewport #}
<img alt="Favicon thumbnail"
class="favicon lazy-favicon"
@@ -74,7 +77,7 @@
{% else %}
{{ watch.get('title') or watch.link }}
{% endif %}
<a class="external" target="_blank" rel="noopener" href="{{ watch.link.replace('source:','') }}">&nbsp;</a>
<a class="external" target="_blank" rel="noopener" href="{{ open_link }}" title="{{ open_link }}">&nbsp;</a>
</span>
{%- for watch_tag_uuid, watch_tag in datastore.get_all_tags_for_watch(watch['uuid']).items() -%}
+25 -12
View File
@@ -56,6 +56,7 @@ from changedetectionio.api import (
WatchSingleHistory,
)
from changedetectionio.api.Search import Search
from changedetectionio.blueprint.menu_modes import MENU_SIDEBAR_ACTIONMODES, MENU_SIDEBAR_ACTIONMODES_DEFAULT
from changedetectionio.favicon_utils import get_favicon_mime_type
from changedetectionio.languages import (
get_available_languages,
@@ -286,20 +287,32 @@ def _filter_url(**overrides):
@app.template_global()
def get_sidebar_mode_class():
"""Body class that drives the left-rail behaviour (see parts/_action_sidebar.scss).
"""Body class(es) that drive the left-rail behaviour (see parts/_action_sidebar.scss).
'collapsed' -> slim icon rail that expands on hover/focus (actionsidebar-minimal)
'pinned' -> rail always expanded with labels visible (actionside-bar-on)
Only the modes offered by MENU_SIDEBAR_ACTIONMODES are honoured - anything else in the
datastore (a stale value from an older release, hand-edited JSON) falls back to
MENU_SIDEBAR_ACTIONMODES_DEFAULT rather than leaking through as a body class.
'expandable' -> icon-only rail, rolls out over the content on hover/focus
'pinned-expanded' -> rail always expanded, labels visible at rest
'minimal' -> icon-only rail that never expands
"""
mode = datastore.data['settings']['application'].get('ui', {}).get('sidebar_mode', 'collapsed')
# Pinned mode is permanently expanded, so it carries 'action-side-bar-expanded'
# from the start. In collapsed mode that class is toggled on hover/focus by
# static/js/sidebar.js.
return (
'actionside-bar-on action-side-bar-expanded'
if mode == 'pinned'
else 'actionsidebar-minimal'
)
# 'actionsidebar-minimal' - collapsed icon rail (hover-to-expand lives in CSS + static/js/sidebar.js)
# 'actionsidebar-no-expand' - opts that rail out of hover-to-expand
# 'actionside-bar-on' - always-open rail
# 'action-side-bar-expanded'- expanded logo/stats block
body_classes = {
'expandable': 'actionsidebar-minimal',
'pinned-expanded': 'actionside-bar-on action-side-bar-expanded',
'minimal': 'actionsidebar-minimal actionsidebar-no-expand',
}
mode = datastore.data['settings']['application'].get('ui', {}).get('sidebar_mode')
if mode not in {choice for choice, _label in MENU_SIDEBAR_ACTIONMODES} or mode not in body_classes:
mode = MENU_SIDEBAR_ACTIONMODES_DEFAULT
return body_classes[mode]
@app.template_global()
+17 -3
View File
@@ -4,6 +4,7 @@ from loguru import logger
from wtforms.widgets.core import TimeInput
from flask_babel import lazy_gettext as _l, gettext
from changedetectionio.blueprint.menu_modes import MENU_SIDEBAR_ACTIONMODES, MENU_SIDEBAR_ACTIONMODES_DEFAULT
from changedetectionio.blueprint.rss import RSS_FORMAT_TYPES, RSS_TEMPLATE_TYPE_OPTIONS, RSS_TEMPLATE_HTML_DEFAULT
from changedetectionio.llm.ui_strings import LLM_INTENT_WATCH_PLACEHOLDER
from changedetectionio.llm.evaluator import (
@@ -939,6 +940,7 @@ class SingleBrowserStep(Form):
class processor_text_json_diff_form(commonSettingsForm):
url = StringField(_l('Web Page URL'), validators=[validateURL()])
link_to_open = StringField(_l('Open Link Override'), validators=[validators.Optional(), validateURL()], default='')
tags = StringTagUUID(_l('Group Tag'), [validators.Optional()], default='')
time_between_check = EnhancedFormField(
@@ -1046,6 +1048,19 @@ class processor_text_json_diff_form(commonSettingsForm):
self.url.errors.append(gettext('Invalid template syntax: %(error)s') % {'error': e})
result = False
# Attempt to validate jinja2 templates in the optional "Link to Open"
if self.link_to_open.data and self.link_to_open.data.strip():
try:
jinja_render(template_str=self.link_to_open.data)
except ModuleNotFoundError as e:
logger.error(e)
self.link_to_open.errors.append(gettext('Invalid template syntax configuration: %(error)s') % {'error': e})
result = False
except Exception as e:
logger.error(e)
self.link_to_open.errors.append(gettext('Invalid template syntax: %(error)s') % {'error': e})
result = False
# Attempt to validate jinja2 templates in the body
if self.body.data and self.body.data.strip():
try:
@@ -1164,9 +1179,8 @@ class globalSettingsApplicationUIForm(Form):
choices=[('long', _l('Long (1 minute ago)')), ('short', _l('Short (1m ago)'))],
default='long', validators=[validators.Optional()])
sidebar_mode = SelectField(_l('Navigation sidebar'),
choices=[('collapsed', _l('Collapsed icon rail (expands on hover)')),
('pinned', _l('Always expanded'))],
default='collapsed', validators=[validators.Optional()])
choices=MENU_SIDEBAR_ACTIONMODES,
default=MENU_SIDEBAR_ACTIONMODES_DEFAULT, validators=[validators.Optional()])
# datastore.data['settings']['application']..
class globalSettingsApplicationForm(commonSettingsForm):
+3
View File
@@ -19,6 +19,7 @@ def get_timeago_locale(flask_locale, short=False):
- Swedish: Flask uses 'sv', timeago uses 'sv_SE'
- Norwegian: Flask uses 'no', timeago uses 'nb_NO' or 'nn_NO'
- Hindi: Flask uses 'hi', timeago uses 'in_HI'
- Indonesian: Flask uses 'id', timeago uses 'in_ID'
- Czech: Flask uses 'cs', but timeago doesn't support Czech - fallback to English
Args:
@@ -46,6 +47,7 @@ def get_timeago_locale(flask_locale, short=False):
'sv': 'sv_SE', # Swedish
'no': 'nb_NO', # Norwegian Bokmål
'hi': 'in_HI', # Hindi
'id': 'in_ID', # Indonesian
'cs': 'en', # Czech not supported by timeago, fallback to English
'ja': 'ja', # Japanese
'uk': 'uk', # Ukrainian
@@ -156,6 +158,7 @@ LANGUAGE_DATA = {
'tr': {'flag': 'fi fi-tr fis', 'name': 'Türkçe'},
'ar': {'flag': 'fi fi-sa fis', 'name': 'العربية'},
'hi': {'flag': 'fi fi-in fis', 'name': 'हिन्दी'},
'id': {'flag': 'fi fi-id fis', 'name': 'Bahasa Indonesia'},
'uk': {'flag': 'fi fi-ua fis', 'name': 'Українська'},
}
+2 -1
View File
@@ -1,6 +1,7 @@
from os import getenv
from copy import deepcopy
from changedetectionio.blueprint.menu_modes import MENU_SIDEBAR_ACTIONMODES_DEFAULT
from changedetectionio.blueprint.rss import RSS_FORMAT_TYPES, RSS_CONTENT_FORMAT_DEFAULT
from changedetectionio.model.Tags import TagsDict
@@ -80,7 +81,7 @@ class model(dict):
'socket_io_enabled': True,
'favicons_enabled': True,
'timeago_format': 'long', # 'long' = "1 minute ago", 'short' = "1m ago"
'sidebar_mode': 'collapsed', # 'collapsed' = slim icon rail, expands on hover; 'pinned' = always expanded
'sidebar_mode': MENU_SIDEBAR_ACTIONMODES_DEFAULT, # one of blueprint.menu_modes.MENU_SIDEBAR_ACTIONMODES
},
}
}
+42 -8
View File
@@ -272,10 +272,12 @@ class model(EntityPersistenceMixin, watch_base):
def has_unviewed(self):
return int(self.newest_history_key) > int(self['last_viewed']) and self.__history_n >= 2
@property
def link(self):
def _resolve_link(self, url, flash_on_template_error=True):
"""Validate, Jinja2-render and de-'source:' a URL so it is safe to put in an href.
url = self.get('url', '')
Returns 'DISABLED' when the URL is missing/unsafe, or '' when its Jinja2 template
could not be rendered.
"""
if not is_safe_valid_url(url):
return 'DISABLED'
@@ -286,11 +288,15 @@ class model(EntityPersistenceMixin, watch_base):
ready_url = jinja_render(template_str=url)
except Exception as e:
logger.critical(f"Invalid URL template for: '{url}' - {str(e)}")
from flask import flash, url_for
from markupsafe import Markup
message = Markup('<a href="{}#general">The URL {} is invalid and cannot be used, click to edit</a>'.format(
url_for('ui.ui_edit.edit_page', uuid=self.get('uuid')), self.get('url', '')))
flash(message, 'error')
# has_request_context() guard: this is also reached from worker threads and the
# notification path, where flash() would raise "Working outside of request context".
from flask import has_request_context
if flash_on_template_error and has_request_context():
from flask import flash, url_for
from markupsafe import Markup
message = Markup('<a href="{}#general">The URL {} is invalid and cannot be used, click to edit</a>'.format(
url_for('ui.ui_edit.edit_page', uuid=self.get('uuid')), url))
flash(message, 'error')
return ''
if ready_url.startswith('source:'):
@@ -301,6 +307,34 @@ class model(EntityPersistenceMixin, watch_base):
return 'DISABLED'
return ready_url
@property
def link(self):
"""The URL that actually gets fetched/checked."""
return self._resolve_link(self.get('url', ''))
@property
def open_link_override(self):
"""The resolved optional 'Link to Open', or '' when unset/unusable."""
override = str(self.get('link_to_open') or '').strip()
if not override:
return ''
# Don't flash on a broken template here - `link` already surfaces that for the watched URL,
# and a bad "Link to Open" should never stop the page rendering.
ready = self._resolve_link(override, flash_on_template_error=False)
if not ready or ready == 'DISABLED':
return ''
return ready
@property
def open_link(self):
"""The URL a human should be sent to when they click through to "the site".
Some watches point at an API endpoint or RSS feed that is useless in a browser -
'Link to Open' lets the user store the real page for those. Falls back to `link`.
"""
return self.open_link_override or self.link
@property
def domain_only_from_link(self):
from urllib.parse import urlparse
+1
View File
@@ -207,6 +207,7 @@ class watch_base(dict):
'last_error': False,
'last_notification_error': None,
'last_viewed': 0, # history key value of the last viewed via the [diff] link
'link_to_open': '', # Optional human-facing URL to open instead of 'url' (eg. watch an API/RSS endpoint, open the real page)
'llm_backend_profile': True, # @note - now its just a bool but in the near future we can select a LLM profile or 'off'/false
'llm_change_summary': '', # Prompt for AI Change Summary — replaces {{ diff }} in notifications
'llm_change_summary_mode': 'replace', # 'replace' the inherited prompt, or 'append' to it
@@ -241,6 +241,7 @@ class NotificationContextData(dict):
'watch_tag': None,
'watch_title': None,
'watch_url': 'https://WATCH-PLACE-HOLDER/',
'watch_open_url': 'https://WATCH-PLACE-HOLDER/', # watch['link_to_open'] when set, otherwise the same as watch_url
'watch_uuid': 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', # Converted to 'watch_uuid' in create_notification_parameters
})
@@ -350,6 +351,7 @@ def set_basic_notification_vars(current_snapshot, prev_snapshot, watch, triggere
'triggered_text': triggered_text,
'uuid': watch.get('uuid') if watch else None,
'watch_url': watch.get('url') if watch else None,
'watch_open_url': (watch.open_link_override or watch.get('url')) if watch else None,
'watch_uuid': watch.get('uuid') if watch else None,
'watch_mime_type': watch.get('content-type')
}
@@ -511,6 +513,7 @@ Thanks - Your omniscient changedetection.io installation.
if 'notification_urls' in n_object:
n_object.update({
'watch_url': watch['url'],
'watch_open_url': watch.open_link_override or watch['url'],
'uuid': watch_uuid,
'screenshot': None
})
@@ -560,6 +563,7 @@ Thanks - Your omniscient changedetection.io installation.
if 'notification_urls' in n_object:
n_object.update({
'watch_url': watch['url'],
'watch_open_url': watch.open_link_override or watch['url'],
'uuid': watch_uuid
})
self.notification_q.put(n_object)
@@ -430,7 +430,7 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect)
change_percentage=change_percentage,
comparison_data=comparison_data, # Full history for charts/visualization
comparison_method=method_display,
current_diff_url=watch['url'],
current_diff_url=watch.open_link,
from_version=from_version,
percentage_different=change_percentage,
threshold=pixel_difference_threshold_sensitivity,
@@ -106,5 +106,5 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect)
uuid=watch.get('uuid'),
versions=versions,
timestamp=timestamp,
current_diff_url=watch['url']
current_diff_url=watch.open_link
)
@@ -191,7 +191,7 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect,
'restock_diff/difference.html',
uuid=uuid,
watch=watch,
current_diff_url=watch['url'],
current_diff_url=watch.open_link,
extra_title=f" - {watch.label} - {gettext('Price history')}",
last_error=watch['last_error'],
screenshot=watch.get_screenshot(),
@@ -216,7 +216,7 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect,
#initial_scroll_line_number=100,
bottom_horizontal_offscreen_contents=offscreen_content,
content=content,
current_diff_url=watch['url'],
current_diff_url=watch.open_link,
diff_cell_grid=diff_cell_grid,
diff_prefs=diff_prefs,
extra_classes=' '.join(filter(None, ['difference-page', 'llm-configured' if llm_configured else ''])),
+3 -1
View File
@@ -78,7 +78,9 @@ $(document).ready(function () {
// bootstrap it, this will trigger everything else
$('#browsersteps-img').bind('load', function () {
$('body').addClass('full-width');
console.log("Loaded background...");
console.log(`Loaded background ${this.naturalWidth}px` );
// For the UI width of the whole edit area
document.documentElement.style.setProperty('--browser-steps-max-width', `${this.naturalWidth+200}px` );
document.getElementById("browsersteps-selector-canvas");
c = document.getElementById("browsersteps-selector-canvas");
+6 -4
View File
@@ -1,13 +1,15 @@
// Left-rail expand/collapse state.
// Adds `action-side-bar-expanded` to <body> whenever the rail is showing its
// labels. In pinned mode (body.actionside-bar-on) the class is already present
// from page load; in collapsed mode (body.actionsidebar-minimal) the rail only
// expands on hover/focus, so we toggle the class to match.
// labels. In 'pinned-expanded' mode (body.actionside-bar-on) the class is already
// present from page load; in 'expandable' mode (body.actionsidebar-minimal) the rail
// only expands on hover/focus, so we toggle the class to match. 'minimal' mode is
// also the collapsed rail but carries `actionsidebar-no-expand` and never rolls out.
(function() {
'use strict';
document.addEventListener('DOMContentLoaded', function() {
if (!document.body.classList.contains('actionsidebar-minimal')) {
if (!document.body.classList.contains('actionsidebar-minimal') ||
document.body.classList.contains('actionsidebar-no-expand')) {
return;
}
@@ -115,7 +115,9 @@ window.initVisualSelector = function (opts) {
$('#selector-current-xpath, #clear-selector').hide();
})
.on('load', () => {
console.log("Loaded background...");
console.log(`Loaded background ${$selectorBackgroundElem[0].naturalWidth}px`);
// For the UI width of the whole edit area
document.documentElement.style.setProperty('--visualselector-max-width', `${$selectorBackgroundElem[0].naturalWidth}px` );
c = $selectorCanvasElem[0];
xctx = c.getContext("2d");
ctx = c.getContext("2d");
@@ -32,21 +32,27 @@ $action-sidebar-content-slot: 1100px;
// Hidden on mobile: the mobile drawer (hamburger) carries these items
display: none;
}
}
body.actionside-bar-on {
.action-sidebar {
/* width: $action-sidebar-width-expanded; // reserve enough to host the expanded inner block*/
// MINIMAL MODE: the rail's footprint in the flex row is frozen at the
// collapsed width (+ the inner block's horizontal padding, which is
// content-box). The inner block is taken out of flow (absolute, below) so
// its hover expansion rolls out ON TOP of the page content instead of
// widening this flex item and reflowing `.app-main`.
body.actionsidebar-minimal & {
flex: 0 0 auto;
width: calc(#{$action-sidebar-width-collapsed} + #{$common-gap * 2});
overflow: visible; // let the expanded inner block escape the frame
}
}
// The actual interactive item block — wraps only the items, not the whole height.
//
// Two body-level modes drive the rail's display state:
// body.actionside-bar-on → always expanded (icons + labels visible all the time)
// body.actionsidebar-minimal → icon-only collapsed rail; expands on hover/focus
// `actionside-bar-on` is the default applied in templates/base.html. Both the
// hover-to-expand width animation and the label fade-in are gated on the
// Body-level classes drive the rail's display state (emitted by
// flask_app.get_sidebar_mode_class() from the `sidebar_mode` setting):
// body.actionside-bar-on → always expanded (icons + labels visible all the time)
// body.actionsidebar-minimal → icon-only collapsed rail; expands on hover/focus
// body.actionsidebar-no-expand → alongside the above: stays collapsed, no hover-out
// Both the hover-to-expand width animation and the label fade-in are gated on the
// minimal class so that the always-expanded mode (or no mode) doesn't trigger
// a needless layout jump when the user mouses across the rail.
.action-sidebar-inner {
@@ -64,12 +70,40 @@ body.actionside-bar-on {
padding-left: $common-gap;
padding-right: $common-gap;
// Hover-to-expand only fires in minimal mode.
body.actionsidebar-minimal &:hover,
body.actionsidebar-minimal &:focus-within {
// MINIMAL MODE: lift the block out of the flex flow so growing it can't
// resize the rail (and therefore can't shift the content column). The rail
// is `position: sticky`, i.e. a positioned element, so top/left/bottom here
// anchor to it; top+bottom keep the column full-height, which the footer
// list's `margin-top: auto` still needs.
//
// Once it floats above the page it needs its own backdrop, or the content
// it rolls over would read straight through the rail's translucent 5% white.
// It can't just be a flat colour: the page background is a fixed, viewport-
// sized gradient (`body:after`), so the rail repaints that same gradient with
// `background-attachment: fixed` — which makes the viewport the positioning
// area — and lays the rail's own 5% white on top. Result: opaque, but
// pixel-identical to the translucent rail it replaces, at rest and rolled out.
body.actionsidebar-minimal & {
position: absolute;
top: 0;
left: 0;
bottom: 0;
z-index: 1;
background-color: var(--color-background-page); // fallback under the gradient
background-image:
linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)),
var(--page-background-gradient);
background-attachment: fixed, fixed;
}
// Hover-to-expand only fires in minimal mode, and not for 'minimal' (no-expand).
body.actionsidebar-minimal:not(.actionsidebar-no-expand) &:hover,
body.actionsidebar-minimal:not(.actionsidebar-no-expand) &:focus-within {
width: $action-sidebar-width-expanded;
// Enter is smoother.
transition: width 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
// Lift it off the page while it's rolled out over the content.
box-shadow: var(--color-sidebar-shadow);
}
// Always-expanded mode: no hover gate; the rail just sits open.
@@ -250,8 +284,8 @@ ul.action-sidebar-list {
// When the inner block expands in MINIMAL mode, reveal every label/badge
// together with a smooth enter. Always-expanded mode handles labels via the
// `body.actionside-bar-on` block below — no hover gate needed there.
body.actionsidebar-minimal .action-sidebar-inner:hover,
body.actionsidebar-minimal .action-sidebar-inner:focus-within {
body.actionsidebar-minimal:not(.actionsidebar-no-expand) .action-sidebar-inner:hover,
body.actionsidebar-minimal:not(.actionsidebar-no-expand) .action-sidebar-inner:focus-within {
.action-sidebar-item {
.action-label {
opacity: 1;
@@ -79,7 +79,6 @@
width: 100%;
overflow-y: scroll;
position: relative;
height: 80vh;
> img {
position: absolute;
@@ -1,7 +1,7 @@
#language-selector-flag {
display: inline-block;
width: 1.2em;
height: 1.2em;
width: 22px;
height: 22px;
vertical-align: middle;
border-radius: 50%;
overflow: hidden;
@@ -16,7 +16,7 @@
}
&.toast-top-center {
top: 100px;
top: 120px;
left: 50%;
transform: translateX(-50%);
}
@@ -27,6 +27,12 @@
--color-background-gradient-first: #5ad8f7;
--color-background-gradient-second: #2f50af;
--color-background-gradient-third: #9150bf;
// The page's atmosphere gradient, shared so anything that needs to sit ON the
// page background (e.g. the left rail rolling out over content) can repaint
// an identical copy instead of approximating it with a flat colour. The
// referenced gradient stops are re-declared per theme below, so this single
// definition follows light/dark automatically.
--page-background-gradient: linear-gradient(130deg, var(--color-background-gradient-first), var(--color-background-gradient-second) 41.07%, var(--color-background-gradient-third) 84.05%);
--color-background: var(--color-white);
--color-text: var(--color-grey-200);
--color-link: #1b98f8;
@@ -1,3 +1,12 @@
body:has(#browser-steps:target) .edit-form {
max-width: min(var(--browser-steps-max-width, 1280px), 95vw);
width: 95%;
}
body:has(#visualselector:target) .edit-form {
width: 95%;
max-width: min(var(--visualselector-max-width, 1280px), 95vw);
}
#selector-wrapper {
height: 100%;
@@ -42,7 +42,8 @@
@use "parts/sub_tabs";
// Smooth transitions for theme switching
body,
// Disabled - people complained
/*body,
.pure-table,
.pure-table thead,
.pure-table td,
@@ -65,7 +66,7 @@ code,
a,
.watch-controls img {
transition: color 0.4s ease, background-color 0.4s ease, background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease;
}
}*/
body {
color: var(--color-text);
@@ -305,7 +306,7 @@ code {
body:after {
content: "";
background: linear-gradient(130deg, var(--color-background-gradient-first), var(--color-background-gradient-second) 41.07%, var(--color-background-gradient-third) 84.05%);
background: var(--page-background-gradient);
}
body:after,
@@ -697,12 +698,12 @@ footer {
}
}
@media only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: $desktop-wide-breakpoint) {
.edit-form {
padding: 0.5em;
margin: 0;
width: 100%;
}
#nav-menu {
File diff suppressed because one or more lines are too long
@@ -25,6 +25,10 @@
<td><code>{{ '{{watch_url}}' }}</code></td>
<td>{{ _('The URL being watched.') }}</td>
</tr>
<tr>
<td><code>{{ '{{watch_open_url}}' }}</code></td>
<td>{{ _('The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API endpoint or RSS feed but you want to link to the real page.') }}</td>
</tr>
<tr>
<td><code>{{ '{{watch_uuid}}' }}</code></td>
<td>{{ _('The UUID of the watch.') }}</td>
+2 -2
View File
@@ -7,12 +7,12 @@
<li class="action-sidebar-li" id="action-sidebar-logo">
{%- if has_password and not current_user.is_authenticated -%}
<a id="cdio-logo" href="https://changedetection.io" rel="noopener">
<a id="cdio-logo" href="https://changedetection.io" rel="noopener" title="ChangeDetection.io intelligent web page change detection.">
<span id="logo-expanded"><strong>Change</strong>Detection.io</span>
<span id="logo-short"><strong>CD</strong>IO</span>
</a>
{%- else -%}
<a id="cdio-logo" href="{{url_for('watchlist.index')}}">
<a id="cdio-logo" href="{{url_for('watchlist.index')}}" title="ChangeDetection.io intelligent web page change detection.">
<span id="logo-expanded"><strong>Change</strong>Detection.io</span>
<span id="logo-short"><strong>CD</strong>IO</span>
</a>
+78
View File
@@ -338,6 +338,84 @@ def test_api_simple(client, live_server, measure_memory_usage, datastore_path):
)
assert len(res.json) == 0, "Watch list should be empty"
def test_api_delete_watch_history(client, live_server, measure_memory_usage, datastore_path):
"""DELETE /api/v1/watch/<uuid>/history should wipe the snapshots but keep the watch (#4397)"""
api_key = live_server.app.config['DATASTORE'].data['settings']['application'].get('api_access_token')
set_original_response(datastore_path=datastore_path)
test_url = url_for('test_endpoint', _external=True)
res = client.post(
url_for("createwatch"),
data=json.dumps({"url": test_url}),
headers={'content-type': 'application/json', 'x-api-key': api_key},
follow_redirects=True
)
assert res.status_code == 201
watch_uuid = res.json.get('uuid')
wait_for_all_checks(client)
# A second snapshot so we know we're clearing more than one
set_modified_response(datastore_path=datastore_path)
client.get(url_for("watch", uuid=watch_uuid, recheck='1'), headers={'x-api-key': api_key})
wait_for_all_checks(client)
res = client.get(
url_for("watchhistory", uuid=watch_uuid),
headers={'x-api-key': api_key},
)
assert len(res.json) == 2, "Should have two history entries before clearing"
# Unknown watch UUID should 404 and not blow up
res = client.delete(
url_for("watchhistory", uuid='4d8b5b4a-8e0b-4d4a-9f57-3f2b1c0d9e11'),
headers={'x-api-key': api_key},
)
assert res.status_code == 404
# Requires the API key
res = client.delete(url_for("watchhistory", uuid=watch_uuid))
assert res.status_code == 403
# Pause it first - clearing resets last_checked to 0 which otherwise makes the ticker
# queue an instant recheck, and that would race with the assertions below
client.get(url_for("watch", uuid=watch_uuid, paused='paused'), headers={'x-api-key': api_key})
# Now really clear it
res = client.delete(
url_for("watchhistory", uuid=watch_uuid),
headers={'x-api-key': api_key},
)
assert res.status_code == 204
res = client.get(
url_for("watchhistory", uuid=watch_uuid),
headers={'x-api-key': api_key},
)
assert res.json == {}, "History should be empty after DELETE"
# The watch itself must survive, with its state reset
res = client.get(
url_for("watch", uuid=watch_uuid),
headers={'x-api-key': api_key}
)
assert res.status_code == 200
assert res.json.get('url') == test_url
assert res.json.get('history_n') == 0
assert res.json.get('last_checked') == 0
assert res.json.get('previous_md5') == False
# And a snapshot fetch now has nothing to give
res = client.get(
url_for("watchsinglehistory", uuid=watch_uuid, timestamp='latest'),
headers={'x-api-key': api_key},
)
assert res.status_code == 404
delete_all_watches(client)
def test_roundtrip_API(client, live_server, measure_memory_usage, datastore_path):
"""
Test the full round trip, this way we test the default Model fits back into OpenAPI spec
@@ -0,0 +1,171 @@
"""
Tests for the optional per-watch "Link to Open" (`link_to_open`).
A watch may point at something that is useless in a browser - an API endpoint or an RSS
feed - while the human-readable page lives somewhere else. `link_to_open` stores that page,
and every "go to the site" affordance (watch list, history/preview header, the
{{watch_open_url}} notification token) uses it in preference to the watched URL.
"""
import os
from flask import url_for
from .util import set_original_response, set_modified_response, wait_for_all_checks, \
wait_for_notification_endpoint_output
OPEN_URL = "https://example.com/the-real-human-page"
def _add_watch(client, test_url):
res = client.post(
url_for("ui.ui_views.form_quick_watch_add"),
data={"url": test_url, "tags": ''},
follow_redirects=True
)
assert b"Watch added" in res.data
wait_for_all_checks(client)
def _edit_watch(client, test_url, **extra):
data = {
"url": test_url,
"tags": "",
"headers": "",
"fetch_backend": "html_requests",
"time_between_check_use_default": "y",
}
data.update(extra)
return client.post(url_for("ui.ui_edit.edit_page", uuid="first"), data=data, follow_redirects=True)
def test_link_to_open_is_saved_and_used_in_the_watch_list(client, live_server, measure_memory_usage, datastore_path):
set_original_response(datastore_path=datastore_path)
test_url = url_for('test_endpoint', _external=True)
_add_watch(client, test_url)
# Without an override, the watch list links to the watched URL
res = client.get(url_for("watchlist.index"))
assert f'href="{test_url}"'.encode('utf-8') in res.data
assert OPEN_URL.encode('utf-8') not in res.data
res = _edit_watch(client, test_url, link_to_open=OPEN_URL)
assert b"Updated watch." in res.data
# It round-trips back into the edit form
res = client.get(url_for("ui.ui_edit.edit_page", uuid="first"))
assert OPEN_URL.encode('utf-8') in res.data
# ...and the watch list now points at it instead of the watched URL
res = client.get(url_for("watchlist.index"))
assert f'href="{OPEN_URL}"'.encode('utf-8') in res.data
assert f'href="{test_url}"'.encode('utf-8') not in res.data
def test_link_to_open_used_in_history_header(client, live_server, measure_memory_usage, datastore_path):
set_original_response(datastore_path=datastore_path)
test_url = url_for('test_endpoint', _external=True)
_add_watch(client, test_url)
set_modified_response(datastore_path=datastore_path)
client.post(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
assert b"Updated watch." in _edit_watch(client, test_url, link_to_open=OPEN_URL).data
uuid = next(iter(client.application.config.get('DATASTORE').data['watching'].keys()))
# Top-of-page link on the history/diff page
res = client.get(url_for("ui.ui_diff.diff_history_page", uuid=uuid))
assert f'class="current-diff-url" href="{OPEN_URL}"'.encode('utf-8') in res.data
# ...and on the preview page
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid))
assert f'class="current-diff-url" href="{OPEN_URL}"'.encode('utf-8') in res.data
def test_invalid_link_to_open_is_rejected(client, live_server, measure_memory_usage, datastore_path):
set_original_response(datastore_path=datastore_path)
test_url = url_for('test_endpoint', _external=True)
_add_watch(client, test_url)
res = _edit_watch(client, test_url, link_to_open="javascript:alert(1)")
assert b"Updated watch." not in res.data
assert b"Watch protocol is not permitted or invalid URL format" in res.data
# Blank is fine - it means "use the watched URL"
res = _edit_watch(client, test_url, link_to_open="")
assert b"Updated watch." in res.data
watch = list(client.application.config.get('DATASTORE').data['watching'].values())[0]
assert watch.open_link == test_url
def test_watch_open_url_notification_token(client, live_server, measure_memory_usage, datastore_path):
set_original_response(datastore_path=datastore_path)
notification_file = os.path.join(datastore_path, "notification.txt")
if os.path.isfile(notification_file):
os.unlink(notification_file)
test_url = url_for('test_endpoint', _external=True)
_add_watch(client, test_url)
notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')
res = _edit_watch(
client,
test_url,
link_to_open=OPEN_URL,
notification_urls=notification_url,
notification_title="Test",
notification_body="watched={{watch_url}}\nopen={{watch_open_url}}",
notification_format="text",
)
assert b"Updated watch." in res.data
wait_for_all_checks(client)
set_modified_response(datastore_path=datastore_path)
client.post(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
assert wait_for_notification_endpoint_output(datastore_path=datastore_path)
with open(notification_file, 'r') as f:
body = f.read()
assert f"watched={test_url}" in body
assert f"open={OPEN_URL}" in body
os.unlink(notification_file)
def test_watch_open_url_token_falls_back_to_watch_url(client, live_server, measure_memory_usage, datastore_path):
"""With no 'Link to Open' set, {{watch_open_url}} must still render the watched URL."""
set_original_response(datastore_path=datastore_path)
notification_file = os.path.join(datastore_path, "notification.txt")
if os.path.isfile(notification_file):
os.unlink(notification_file)
test_url = url_for('test_endpoint', _external=True)
_add_watch(client, test_url)
notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')
res = _edit_watch(
client,
test_url,
notification_urls=notification_url,
notification_title="Test",
notification_body="open={{watch_open_url}}",
notification_format="text",
)
assert b"Updated watch." in res.data
wait_for_all_checks(client)
set_modified_response(datastore_path=datastore_path)
client.post(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
assert wait_for_notification_endpoint_output(datastore_path=datastore_path)
with open(notification_file, 'r') as f:
body = f.read()
assert f"open={test_url}" in body
os.unlink(notification_file)
@@ -370,6 +370,23 @@ class TestHistoryPathTraversal(unittest.TestCase):
history = watch.history
self.assertEqual(history, {}, "Path traversal entry must be rejected")
def test_parent_dir_entry_is_rejected_by_the_containment_check(self):
"""A bare '..' is the shortest entry that reaches and fails the containment check.
os.path.basename() reduces '/etc/passwd' and '../../etc/passwd' to
'passwd', so those two stop at the os.path.exists() check below the
guard rather than at the guard itself. '..' survives basename() intact
and resolves to the parent of data_dir, which does exist, so the
containment check is what rejects it. Not the only such input —
'../..' and 'foo/..' collapse to the same thing — and not the only
reason the check exists: it also blocks a filename inside data_dir
that is itself a symlink pointing outside.
"""
watch = self._make_watch()
self._write_history_txt(watch, ['1000000000,..\n'])
history = watch.history
self.assertEqual(history, {}, "Parent-directory entry must be rejected")
def test_normal_snapshot_entry_is_accepted(self):
"""A bare filename written by save_history_blob must still load correctly."""
import uuid as uuid_builder
@@ -377,8 +394,11 @@ class TestHistoryPathTraversal(unittest.TestCase):
watch.save_history_blob(contents="hello world", timestamp=1000000000, snapshot_id=str(uuid_builder.uuid4()))
history = watch.history
self.assertEqual(len(history), 1, "Normal snapshot entry must be accepted")
# Watch.history resolves entries with os.path.realpath, so compare against a
# resolved data_dir. On macOS the datastore lives under /tmp, which is a symlink
# to /private/tmp, and an unresolved comparison fails there for a correct path.
self.assertTrue(
list(history.values())[0].startswith(watch.data_dir),
list(history.values())[0].startswith(os.path.realpath(watch.data_dir)),
"Resolved path must be inside the watch data directory"
)
@@ -408,5 +428,47 @@ class TestHistoryPathTraversal(unittest.TestCase):
)
class TestLinkToOpen(unittest.TestCase):
"""`link_to_open` - the optional human-facing URL used instead of the watched URL."""
def _make_watch(self, **fields):
mock_datastore = {'settings': {'application': {}}, 'watching': {}}
return Watch.model(datastore_path='/tmp', __datastore=mock_datastore, default=fields)
def test_defaults_to_the_watched_url(self):
watch = self._make_watch(url='https://example.com/feed.xml')
assert watch.open_link_override == ''
assert watch.open_link == 'https://example.com/feed.xml'
assert watch.open_link == watch.link
def test_override_wins_when_set(self):
watch = self._make_watch(url='https://example.com/api/v1/items.json',
link_to_open='https://example.com/shop/items')
assert watch.link == 'https://example.com/api/v1/items.json'
assert watch.open_link == 'https://example.com/shop/items'
assert watch.open_link_override == 'https://example.com/shop/items'
def test_whitespace_only_override_is_ignored(self):
watch = self._make_watch(url='https://example.com/feed.xml', link_to_open=' ')
assert watch.open_link_override == ''
assert watch.open_link == 'https://example.com/feed.xml'
def test_unsafe_override_falls_back_to_the_watched_url(self):
watch = self._make_watch(url='https://example.com/feed.xml',
link_to_open='javascript:alert(1)')
assert watch.open_link_override == ''
assert watch.open_link == 'https://example.com/feed.xml'
def test_source_prefix_is_stripped_from_the_override(self):
watch = self._make_watch(url='source:https://example.com/feed.xml',
link_to_open='source:https://example.com/page')
assert watch.open_link == 'https://example.com/page'
def test_jinja2_in_the_override_is_rendered(self):
watch = self._make_watch(url='https://example.com/feed.xml',
link_to_open='https://example.com/page?id={{ 1+1 }}')
assert watch.open_link == 'https://example.com/page?id=2'
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -198,6 +198,7 @@ Never fix one language and move on.
| `en_US` | English (US) |
| `es` | Spanish (Español) |
| `fr` | French (Français) |
| `id` | Indonesian (Bahasa Indonesia) |
| `it` | Italian (Italiano) |
| `ja` | Japanese (日本語) |
| `ko` | Korean (한국어) |
@@ -376,6 +376,18 @@ msgstr "Znovu zkontrolovat po (minuty)"
msgid "Import"
msgstr "Importovat"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1947,15 +1959,17 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Interval/doba mezi jednotlivými kontrolami."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Interval/doba mezi jednotlivými kontrolami."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
msgstr ""
@@ -2054,6 +2068,12 @@ msgstr "musíte"
msgid "Set the fetch method"
msgstr "Nastavte metodu načítání"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3188,6 +3208,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3461,14 +3485,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Kontrola zabezpečení přístupového tokenu API povolena"
@@ -4020,6 +4036,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "UUID monitoru."
@@ -382,6 +382,18 @@ msgstr "Nachprüfzeit (Minuten)"
msgid "Import"
msgstr "IMPORT"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1974,16 +1986,16 @@ msgstr ""
"Beschreibung verwenden."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Das Intervall/die Zeitdauer zwischen den einzelnen Überprüfungen."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Sendet eine Benachrichtigung, wenn der Filter auf der Seite nicht mehr sichtbar ist. So wissen Sie, wann sich die "
"Seite geändert hat und Ihr Filter nicht mehr funktioniert."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Das Intervall/die Zeitdauer zwischen den einzelnen Überprüfungen."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2087,6 +2099,14 @@ msgstr "Das musst du"
msgid "Set the fetch method"
msgstr "Legen Sie die Fetch-Methode fest"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Sendet eine Benachrichtigung, wenn der Filter auf der Seite nicht mehr sichtbar ist. So wissen Sie, wann sich die "
"Seite geändert hat und Ihr Filter nicht mehr funktioniert."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3237,6 +3257,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3511,14 +3535,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Sicherheitsüberprüfung des API-Zugriffstokens aktiviert"
@@ -4072,6 +4088,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "Die UUID der Überwachung."
@@ -374,6 +374,18 @@ msgstr ""
msgid "Import"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1941,13 +1953,15 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
@@ -2048,6 +2062,12 @@ msgstr ""
msgid "Set the fetch method"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3180,6 +3200,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3453,14 +3477,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr ""
@@ -4012,6 +4028,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr ""
@@ -374,6 +374,18 @@ msgstr ""
msgid "Import"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1941,13 +1953,15 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
@@ -2048,6 +2062,12 @@ msgstr ""
msgid "Set the fetch method"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3180,6 +3200,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3453,14 +3477,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr ""
@@ -4012,6 +4028,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr ""
@@ -380,6 +380,18 @@ msgstr "Tiempo de revisión (minutos)"
msgid "Import"
msgstr "Importar"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1992,16 +2004,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "Utiliza automáticamente el título de la página si la encuentra, también puede usar su propio título/descripción aquí"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "El intervalo/cantidad de tiempo entre cada verificación."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Envía una notificación cuando el filtro ya no se puede ver en la página, lo cual es bueno para saber cuándo cambió la"
" página y su filtro ya no funcionará."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "El intervalo/cantidad de tiempo entre cada verificación."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2103,6 +2115,14 @@ msgstr "Necesitas"
msgid "Set the fetch method"
msgstr "Establecer el método de recuperación"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Envía una notificación cuando el filtro ya no se puede ver en la página, lo cual es bueno para saber cuándo cambió la"
" página y su filtro ya no funcionará."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "Utilice el botón de verificación ( ✓ ) para probar si una condición se cumple con la instantánea actual."
@@ -3253,6 +3273,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3526,14 +3550,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Comprobación de seguridad del token de acceso API habilitada"
@@ -4085,6 +4101,12 @@ msgstr "La URL de la instancia de changetection.io que está ejecutando."
msgid "The URL being watched."
msgstr "La URL que se está viendo."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "El UUID del monitor."
@@ -378,6 +378,18 @@ msgstr "Temps de revérification (minutes)"
msgid "Import"
msgstr "IMPORTER"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1952,15 +1964,17 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "L'intervalle/la durée entre chaque vérification."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "L'intervalle/la durée entre chaque vérification."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
msgstr ""
@@ -2059,6 +2073,12 @@ msgstr "Vous devez"
msgid "Set the fetch method"
msgstr "Définir la méthode de récupération"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3193,6 +3213,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3466,14 +3490,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Contrôle de sécurité du jeton d'accès à l'API activé"
@@ -4025,6 +4041,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "L'UUID du moniteur."
File diff suppressed because it is too large Load Diff
@@ -376,6 +376,18 @@ msgstr "Tempo di ricontrollo (minuti)"
msgid "Import"
msgstr "Importa"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1943,13 +1955,15 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
@@ -2050,6 +2064,12 @@ msgstr ""
msgid "Set the fetch method"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3182,6 +3202,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3455,14 +3479,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Controllo sicurezza token API attivo"
@@ -4014,6 +4030,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "L'UUID del monitor."
@@ -378,6 +378,18 @@ msgstr "再チェック時間(分)"
msgid "Import"
msgstr "インポート"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1952,14 +1964,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "ページタイトルが見つかった場合は自動的に使用されます。ここで独自のタイトル/説明を使用することもできます。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "各チェック間の間隔/時間。"
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "フィルタがページで見つからなくなったときに通知を送信します。ページが変更されてフィルタが機能しなくなったことを把握するのに便利です。"
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "各チェック間の間隔/時間。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2059,6 +2073,12 @@ msgstr "次の操作が必要です:"
msgid "Set the fetch method"
msgstr "取得方式を設定する"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "フィルタがページで見つからなくなったときに通知を送信します。ページが変更されてフィルタが機能しなくなったことを把握するのに便利です。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "確認ボタン(✓)を使用して、現在のスナップショットに対して条件が満たされるかテストできます。"
@@ -3199,6 +3219,10 @@ msgstr "値"
msgid "Web Page URL"
msgstr "ウェブページ URL"
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr "グループタグ"
@@ -3472,14 +3496,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "APIアクセストークンのセキュリティチェックを有効化"
@@ -4031,6 +4047,12 @@ msgstr "実行中の changedetection.io インスタンスのURL。"
msgid "The URL being watched."
msgstr "監視中のURL。"
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "ウォッチのUUID。"
@@ -376,6 +376,18 @@ msgstr "재확인 시간(분)"
msgid "Import"
msgstr "가져오기"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1951,14 +1963,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "페이지 제목을 찾으면 자동으로 사용하며, 여기에서 직접 제목/설명을 입력할 수도 있습니다."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "각 확인 사이의 간격/시간입니다."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "필터가 페이지에 더 이상 보이지 않을 때 알림을 보냅니다. 페이지가 바뀌어 필터가 더 이상 동작하지 않는 상황을 파악하는 데 유용합니다."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "각 확인 사이의 간격/시간입니다."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2058,6 +2072,12 @@ msgstr "먼저"
msgid "Set the fetch method"
msgstr "가져오기 방식을"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "필터가 페이지에 더 이상 보이지 않을 때 알림을 보냅니다. 페이지가 바뀌어 필터가 더 이상 동작하지 않는 상황을 파악하는 데 유용합니다."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "체크(✓) 버튼을 눌러 현재 스냅샷에서 조건이 통과하는지 테스트해 보세요."
@@ -3190,6 +3210,10 @@ msgstr "값"
msgid "Web Page URL"
msgstr "웹페이지 URL"
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr "그룹 태그"
@@ -3463,14 +3487,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "API 액세스 토큰 보안 확인 활성화"
@@ -4022,6 +4038,12 @@ msgstr "현재 사용 중인 changedetection.io 인스턴스의 URL입니다."
msgid "The URL being watched."
msgstr "모니터링 중인 URL입니다."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "모니터링 UUID입니다."
+35 -13
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.60.3\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-09-04 14:11+0200\n"
"POT-Creation-Date: 2026-09-09 16:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -373,6 +373,18 @@ msgstr ""
msgid "Import"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1940,13 +1952,15 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
@@ -2047,6 +2061,12 @@ msgstr ""
msgid "Set the fetch method"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr ""
@@ -3179,6 +3199,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3452,14 +3476,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr ""
@@ -4011,6 +4027,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr ""
@@ -384,6 +384,18 @@ msgstr "Czas ponownej kontroli (w minutach)"
msgid "Import"
msgstr "Importuj"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr "Zawsze rozwinięte"
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -2077,16 +2089,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "Jeśli tytuł strony zostanie znaleziony, zostanie on automatycznie użyty; można tu również wpisać własny tytuł/opis"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Odstęp czasowy między kolejnymi sprawdzeniami."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Wysyła powiadomienie, gdy filtr przestaje być widoczny na stronie; przydaje się to do stwierdzenia, kiedy strona "
"uległa zmianie i filtr przestanie działać."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Odstęp czasowy między kolejnymi sprawdzeniami."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2190,6 +2202,14 @@ msgstr "Musisz"
msgid "Set the fetch method"
msgstr "Ustaw metodę pobierania"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Wysyła powiadomienie, gdy filtr przestaje być widoczny na stronie; przydaje się to do stwierdzenia, kiedy strona "
"uległa zmianie i filtr przestanie działać."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "Użyj przycisku „Sprawdź” (✓), aby sprawdzić, czy warunek jest spełniony w odniesieniu do bieżącej migawki."
@@ -3339,6 +3359,10 @@ msgstr "Wartość"
msgid "Web Page URL"
msgstr "Adres URL strony internetowej"
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr "Etykieta grupy"
@@ -3612,14 +3636,6 @@ msgstr "Krótki (1 minuta temu)"
msgid "Navigation sidebar"
msgstr "Pasek nawigacyjny"
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr "Zwiń pasek ikon (rozwija się po najechaniu kursorem)"
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr "Zawsze rozwinięte"
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Włączono kontrolę bezpieczeństwa tokenu dostępu do API"
@@ -4173,6 +4189,12 @@ msgstr "Adres URL instancji serwisu changedetection.io, którą uruchomiłeś."
msgid "The URL being watched."
msgstr "Obserwowany adres URL."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "Identyfikator UUID obserwacji."
@@ -377,6 +377,18 @@ msgstr "Tempo de rechecagem (minutos)"
msgid "Import"
msgstr "Importar"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1977,16 +1989,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "Usa automaticamente o título da página se encontrado. Você também pode usar seu próprio título/descrição aqui"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "O intervalo/quantidade de tempo entre cada verificação."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Envia uma notificação quando o filtro não pode mais ser visto na página, útil para saber quando a página mudou e seu "
"filtro não funcionará mais."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "O intervalo/quantidade de tempo entre cada verificação."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2088,6 +2100,14 @@ msgstr "Você precisa"
msgid "Set the fetch method"
msgstr "Definir o método de busca"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Envia uma notificação quando o filtro não pode mais ser visto na página, útil para saber quando a página mudou e seu "
"filtro não funcionará mais."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "Use o botão de verificar (✓) para testar se uma condição passa contra o instantâneo atual."
@@ -3230,6 +3250,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3503,14 +3527,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Verificação de segurança do token de acesso à API ativada"
@@ -4062,6 +4078,12 @@ msgstr "A URL da instância do changedetection.io que você está rodando."
msgid "The URL being watched."
msgstr "A URL que está sendo monitorada."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "O UUID do monitoramento."
@@ -381,6 +381,18 @@ msgstr "Время перепроверки (минуты)"
msgid "Import"
msgstr "Импорт"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -2039,16 +2051,16 @@ msgstr ""
"заголовок/описание."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Интервал/количество времени между каждой проверкой."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Отправляет уведомление, когда фильтр больше не виден на странице. Это полезно для того, чтобы узнать, когда страница "
"изменилась и ваш фильтр больше не будет работать."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Интервал/количество времени между каждой проверкой."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2150,6 +2162,14 @@ msgstr "Вам нужно"
msgid "Set the fetch method"
msgstr "Установите метод выборки"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Отправляет уведомление, когда фильтр больше не виден на странице. Это полезно для того, чтобы узнать, когда страница "
"изменилась и ваш фильтр больше не будет работать."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "Используйте кнопку проверки (✓), чтобы проверить, соответствует ли условие текущему снимку."
@@ -3298,6 +3318,10 @@ msgstr "Ценить"
msgid "Web Page URL"
msgstr "URL-адрес веб-страницы"
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr "Групповой тег"
@@ -3571,14 +3595,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Проверка безопасности токена доступа к API включена"
@@ -4130,6 +4146,12 @@ msgstr "URL-адрес запущенного вами экземпляраchang
msgid "The URL being watched."
msgstr "URL-адрес, который просматривается."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "UUID часов."
@@ -383,6 +383,18 @@ msgstr "Yeniden kontrol süresi (dakika)"
msgid "Import"
msgstr "İçe Aktar"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1980,16 +1992,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "Bulunursa otomatik olarak sayfa başlığını kullanır, ayrıca kendi başlığınızı/açıklamanızı da burada kullanabilirsiniz"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Her kontrol arasındaki zaman aralığı/miktarı."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Filtre artık sayfada görülemediğinde bir bildirim gönderir, sayfanın ne zaman değiştiğini ve filtrenizin artık "
"çalışmayacağını bilmek için iyidir."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Her kontrol arasındaki zaman aralığı/miktarı."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2091,6 +2103,14 @@ msgstr "Yapmanız gerekenler"
msgid "Set the fetch method"
msgstr "Getirme yöntemini ayarla"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Filtre artık sayfada görülemediğinde bir bildirim gönderir, sayfanın ne zaman değiştiğini ve filtrenizin artık "
"çalışmayacağını bilmek için iyidir."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "Bir koşulun mevcut anlık görüntüye göre geçip geçmediğini test etmek için doğrula (✓) düğmesini kullanın."
@@ -3233,6 +3253,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3506,14 +3530,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "API erişim belirteci güvenlik kontrolü etkin"
@@ -4065,6 +4081,12 @@ msgstr "Çalıştırdığınız changedetection.io örneğinin URL'si."
msgid "The URL being watched."
msgstr "İzlenen URL."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "İzleyicinin UUID'si."
@@ -375,6 +375,18 @@ msgstr "Час перевірки (хвилини)"
msgid "Import"
msgstr "Імпорт"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1961,16 +1973,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "Автоматично використовує заголовок сторінки, якщо знайдено. Ви також можете вказати тут свою назву/опис."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Інтервал часу між перевірками."
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
"Надсилає сповіщення, коли фільтр більше не видно на сторінці. Корисно, щоб дізнатися, що сторінка змінилася і ваш "
"фільтр більше не працює."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "Інтервал часу між перевірками."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2072,6 +2084,14 @@ msgstr "Вам потрібно"
msgid "Set the fetch method"
msgstr "Встановити метод завантаження"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr ""
"Надсилає сповіщення, коли фільтр більше не видно на сторінці. Корисно, щоб дізнатися, що сторінка змінилася і ваш "
"фільтр більше не працює."
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "Використовуйте кнопку перевірки (✓), щоб протестувати умову на поточному знімку."
@@ -3212,6 +3232,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3485,14 +3509,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "Перевірку безпеки токена доступу API увімкнено"
@@ -4044,6 +4060,12 @@ msgstr "URL екземпляра changedetection.io, який ви викори
msgid "The URL being watched."
msgstr "URL, за яким ведеться спостереження."
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "UUID завдання."
@@ -379,6 +379,18 @@ msgstr "复检间隔(分钟)"
msgid "Import"
msgstr "导入"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1946,14 +1958,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "若检测到页面标题将自动使用,你也可以在此自定义标题/描述"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "每次检查之间的时间间隔。"
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "当页面上找不到该过滤器时发送通知,便于知晓页面已变化且过滤器不再适用。"
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "每次检查之间的时间间隔。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2053,6 +2067,12 @@ msgstr "你需要"
msgid "Set the fetch method"
msgstr "设置抓取方式"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "当页面上找不到该过滤器时发送通知,便于知晓页面已变化且过滤器不再适用。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "使用验证(✓)按钮测试条件是否符合当前快照。"
@@ -3186,6 +3206,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3459,14 +3483,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "已启用 API 访问令牌安全检查"
@@ -4018,6 +4034,12 @@ msgstr "你正在运行的 changedetection.io 实例的 URL。"
msgid "The URL being watched."
msgstr "被监控的 URL。"
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "监视器的UUID。"
@@ -378,6 +378,18 @@ msgstr "複查時間(分鐘)"
msgid "Import"
msgstr "匯入"
#: changedetectionio/blueprint/menu_modes.py
msgid "Expand on hover"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/blueprint/menu_modes.py
msgid "Stays minimal"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
@@ -1945,14 +1957,16 @@ msgid "Automatically uses the page title if found, you can also use your own tit
msgstr "如果找到頁面標題將自動使用,您也可以在此使用您自己的標題 / 描述"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "每次檢查之間的間隔 / 時間量。"
msgid "Extra Page Title and Link options"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "當頁面上找不到過濾器時發送通知,這有助於了解頁面何時變更導致您的過濾器失效。"
msgid "Optional - links in the list, history and notifications open this URL instead. Does not change the URL being checked."
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "The interval/amount of time between each check."
msgstr "每次檢查之間的間隔 / 時間量。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Set to empty to use system settings default"
@@ -2052,6 +2066,12 @@ msgstr "您需要"
msgid "Set the fetch method"
msgstr "設定抓取方式"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid ""
"Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and "
"your filter will not work anymore."
msgstr "當頁面上找不到過濾器時發送通知,這有助於了解頁面何時變更導致您的過濾器失效。"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the verify (✓) button to test if a condition passes against the current snapshot."
msgstr "使用驗證 (✓) 按鈕測試條件是否符合目前的快照。"
@@ -3186,6 +3206,10 @@ msgstr ""
msgid "Web Page URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Open Link Override"
msgstr ""
#: changedetectionio/forms.py
msgid "Group Tag"
msgstr ""
@@ -3459,14 +3483,6 @@ msgstr ""
msgid "Navigation sidebar"
msgstr ""
#: changedetectionio/forms.py
msgid "Collapsed icon rail (expands on hover)"
msgstr ""
#: changedetectionio/forms.py
msgid "Always expanded"
msgstr ""
#: changedetectionio/forms.py
msgid "API access token security check enabled"
msgstr "已啟用 API 存取權杖安全檢查"
@@ -4018,6 +4034,12 @@ msgstr ""
msgid "The URL being watched."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid ""
"The Open Link Override set on the watch, otherwise the same as watch_url. Use this when the watched URL is an API "
"endpoint or RSS feed but you want to link to the real page."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "The UUID of the watch."
msgstr "監測任務的 UUID。"
+48 -1
View File
@@ -28,7 +28,7 @@ info:
For example: `x-api-key: YOUR_API_KEY`
version: 0.1.8
version: 0.1.9
contact:
name: ChangeDetection.io
url: https://github.com/dgtlmoon/changedetection.io
@@ -293,6 +293,13 @@ components:
format: uri
description: URL to monitor for changes
maxLength: 5000
link_to_open:
type: string
description: |
Optional human-facing URL to open instead of `url`. Use when `url` points at something
that is not meant to be read in a browser (an API endpoint or RSS feed, for example).
An empty string means "use `url`". Exposed rendered as `open_link`.
maxLength: 5000
title:
type: [string, 'null']
description: Custom title for the web page change monitor (watch), not to be confused with page_title
@@ -719,6 +726,12 @@ components:
description: The watch URL rendered in case of any Jinja2 markup, always use this for listing.
readOnly: true
x-computed: true
open_link:
type: string
format: string
description: The URL a human should be sent to - `link_to_open` when set, otherwise the same as `link`.
readOnly: true
x-computed: true
page_title:
type: [string, 'null']
description: HTML <title> tag extracted from the page
@@ -1271,6 +1284,40 @@ paths:
'404':
description: Web page change monitor (watch) not found
delete:
operationId: deleteWatchHistory
tags: [Watch History]
summary: Delete watch history
description: |
Delete/clear all historical snapshots for a web page change monitor (watch), the watch itself is kept.
This also resets the "last checked"/"last changed" state, so the watch will be re-checked on the next cycle.
x-code-samples:
- lang: 'curl'
source: |
curl -X DELETE "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history" \
-H "x-api-key: YOUR_API_KEY"
- lang: 'Python'
source: |
import requests
headers = {'x-api-key': 'YOUR_API_KEY'}
uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f'
response = requests.delete(f'http://localhost:5000/api/v1/watch/{uuid}/history', headers=headers)
print(response.status_code)
parameters:
- name: uuid
in: path
required: true
description: Web page change monitor (watch) unique ID
schema:
type: string
format: uuid
responses:
'204':
description: History deleted successfully
'404':
description: Web page change monitor (watch) not found
/watch/{uuid}/history/{timestamp}:
get:
operationId: getWatchSnapshot
+24 -6
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -40,7 +40,7 @@ orjson~=3.11
# jq not available on Windows so must be installed manually
# Notification library
apprise==1.9.9
apprise==1.13.1
diff_match_patch