mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-01-17 04:30:22 +00:00
Compare commits
10 Commits
api-diff-e
...
0.52.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba4ed9cf27 | ||
|
|
33b7f1684d | ||
|
|
3d14df6a11 | ||
|
|
08ce1e28ce | ||
|
|
e4118a1620 | ||
|
|
64d0c09b08 | ||
|
|
008e5eb024 | ||
|
|
e6553065fd | ||
|
|
de996a4566 | ||
|
|
4784ae4cd0 |
@@ -183,6 +183,9 @@ docker compose pull && docker compose up -d
|
||||
|
||||
See the wiki for more information https://github.com/dgtlmoon/changedetection.io/wiki
|
||||
|
||||
## Different browser viewport sizes (mobile, desktop etc)
|
||||
|
||||
If you are using the recommended `sockpuppetbrowser` (which is in the docker-compose.yml as a setting to be uncommented) you can easily set different viewport sizes for your web page change detection, [see more information here about setting up different viewport sizes](https://github.com/dgtlmoon/sockpuppetbrowser?tab=readme-ov-file#setting-viewport-size).
|
||||
|
||||
## Filters
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
||||
# Semver means never use .01, or 00. Should be .1.
|
||||
__version__ = '0.51.4'
|
||||
__version__ = '0.52.1'
|
||||
|
||||
from changedetectionio.strtobool import strtobool
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
@@ -64,8 +64,17 @@ class Watch(Resource):
|
||||
@validate_openapi_request('getWatch')
|
||||
def get(self, uuid):
|
||||
"""Get information about a single watch, recheck, pause, or mute."""
|
||||
import time
|
||||
from copy import deepcopy
|
||||
watch = deepcopy(self.datastore.data['watching'].get(uuid))
|
||||
watch = None
|
||||
for _ in range(20):
|
||||
try:
|
||||
watch = deepcopy(self.datastore.data['watching'].get(uuid))
|
||||
break
|
||||
except RuntimeError:
|
||||
# Incase dict changed, try again
|
||||
time.sleep(0.01)
|
||||
|
||||
if not watch:
|
||||
abort(404, message='No watch exists with the UUID of {}'.format(uuid))
|
||||
|
||||
|
||||
@@ -85,9 +85,7 @@
|
||||
|
||||
<div class="tab-pane-inner" id="notifications">
|
||||
<fieldset>
|
||||
<div class="field-group">
|
||||
{{ render_common_settings_form(form.application.form, emailprefix, settings_application, extra_notification_token_placeholder_info) }}
|
||||
</div>
|
||||
{{ render_common_settings_form(form.application.form, emailprefix, settings_application, extra_notification_token_placeholder_info) }}
|
||||
</fieldset>
|
||||
<div class="pure-control-group" id="notification-base-url">
|
||||
{{ render_field(form.application.form.base_url, class="m-d") }}
|
||||
@@ -128,7 +126,7 @@
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.requests.form.timeout) }}
|
||||
<span class="pure-form-message-inline">For regular plain requests (not chrome based), maximum number of seconds until timeout, 1-999.<br>
|
||||
<span class="pure-form-message-inline">For regular plain requests (not chrome based), maximum number of seconds until timeout, 1-999.</span><br>
|
||||
</div>
|
||||
<div class="pure-control-group inline-radio">
|
||||
{{ render_field(form.requests.form.default_ua) }}
|
||||
@@ -219,7 +217,7 @@ nav
|
||||
<a id="chrome-extension-link"
|
||||
title="Try our new Chrome Extension!"
|
||||
href="https://chromewebstore.google.com/detail/changedetectionio-website/kefcfmgmlhmankjmnbijimhofdjekbop">
|
||||
<img alt="Chrome store icon" src="{{ url_for('static_content', group='images', filename='google-chrome-icon.png') }}" alt="Chrome">
|
||||
<img alt="Chrome store icon" src="{{ url_for('static_content', group='images', filename='google-chrome-icon.png') }}" >
|
||||
Chrome Webstore
|
||||
</a>
|
||||
</p>
|
||||
@@ -260,14 +258,14 @@ nav
|
||||
Ensure the settings below are correct, they are used to manage the time schedule for checking your web page watches.
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
<p><strong>UTC Time & Date from Server:</strong> <span id="utc-time" >{{ utc_time }}</span></p>
|
||||
<p><strong>Local Time & Date in Browser:</strong> <span class="local-time" data-utc="{{ utc_time }}"></span></p>
|
||||
<p>
|
||||
<p><strong>UTC Time & Date from Server:</strong> <span id="utc-time" >{{ utc_time }}</span></p>
|
||||
<p><strong>Local Time & Date in Browser:</strong> <span class="local-time" data-utc="{{ utc_time }}"></span></p>
|
||||
<div>
|
||||
{{ render_field(form.application.form.scheduler_timezone_default) }}
|
||||
<datalist id="timezones" style="display: none;">
|
||||
{%- for timezone in available_timezones -%}<option value="{{ timezone }}">{{ timezone }}</option>{%- endfor -%}
|
||||
</datalist>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane-inner" id="ui-options">
|
||||
@@ -336,7 +334,7 @@ nav
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.
|
||||
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.</p>
|
||||
|
||||
<div class="pure-control-group" id="extra-proxies-setting">
|
||||
{{ render_fieldlist_with_inline_errors(form.requests.form.extra_proxies) }}
|
||||
|
||||
@@ -118,6 +118,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
sent_obj = process_notification(n_object, datastore)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
e_str = str(e)
|
||||
# Remove this text which is not important and floods the container
|
||||
e_str = e_str.replace(
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="diff-jump">
|
||||
<div id="diff-jump" style="display:none;"><!-- disabled for now -->
|
||||
<a id="jump-next-diff" title="{{ _('Jump to next difference') }}">{{ _('Jump') }}</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
datastore=datastore,
|
||||
errored_count=errored_count,
|
||||
form=form,
|
||||
generate_tag_colors=processors.generate_processor_badge_colors,
|
||||
guid=datastore.data['app_guid'],
|
||||
has_proxies=datastore.proxy_list,
|
||||
hosted_sticky=os.getenv("SALTED_PASS", False) == False,
|
||||
|
||||
@@ -22,6 +22,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
/* Auto-generated processor badge colors */
|
||||
{{ processor_badge_css|safe }}
|
||||
|
||||
/* Auto-generated tag colors */
|
||||
{%- for uuid, tag in tags -%}
|
||||
{%- if tag and tag.title -%}
|
||||
{%- set class_name = tag.title|sanitize_tag_class -%}
|
||||
{%- set colors = generate_tag_colors(tag.title) -%}
|
||||
.button-tag.tag-{{ class_name }} {
|
||||
background-color: {{ colors['light']['bg'] }};
|
||||
color: {{ colors['light']['color'] }};
|
||||
}
|
||||
|
||||
.watch-tag-list.tag-{{ class_name }} {
|
||||
background-color: {{ colors['light']['bg'] }};
|
||||
color: {{ colors['light']['color'] }};
|
||||
}
|
||||
|
||||
html[data-darkmode="true"] .button-tag.tag-{{ class_name }} {
|
||||
background-color: {{ colors['dark']['bg'] }};
|
||||
color: {{ colors['dark']['color'] }};
|
||||
}
|
||||
|
||||
html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
|
||||
background-color: {{ colors['dark']['bg'] }};
|
||||
color: {{ colors['dark']['color'] }};
|
||||
}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</style>
|
||||
<div class="box" id="form-quick-watch-add">
|
||||
|
||||
@@ -82,7 +109,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<!-- tag list -->
|
||||
{%- for uuid, tag in tags -%}
|
||||
{%- if tag != "" -%}
|
||||
<a href="{{url_for('watchlist.index', tag=uuid) }}" class="pure-button button-tag {{'active' if active_tag_uuid == uuid }}">{{ tag.title }}</a>
|
||||
<a href="{{url_for('watchlist.index', tag=uuid) }}" class="pure-button button-tag tag-{{ tag.title|sanitize_tag_class }} {{'active' if active_tag_uuid == uuid }}">{{ tag.title }}</a>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
@@ -169,7 +196,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<div class="flex-wrapper">
|
||||
{% if 'favicons_enabled' not in ui_settings or ui_settings['favicons_enabled'] %}
|
||||
<div>{# A page might have hundreds of these images, set IMG options for lazy loading, don't set SRC if we dont have it so it doesnt fetch the placeholder' #}
|
||||
<img alt="Favicon thumbnail" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src='data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="7.087" height="7.087" viewBox="0 0 7.087 7.087"%3E%3Ccircle cx="3.543" cy="3.543" r="3.279" stroke="%23e1e1e1" stroke-width="0.45" fill="none" opacity="0.74"/%3E%3C/svg%3E' {% endif %} />
|
||||
<img alt="Favicon thumbnail" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src='data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="7.087" height="7.087" viewBox="0 0 7.087 7.087"%3E%3Ccircle cx="3.543" cy="3.543" r="3.279" stroke="%23e1e1e1" stroke-width="0.45" fill="none" opacity="0.74"/%3E%3C/svg%3E' {% endif %} >
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
@@ -191,7 +218,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<span class="processor-badge processor-badge-{{ watch['processor'] }}" title="{{ processor_descriptions.get(watch['processor'], watch['processor']) }}">{{ processor_badge_texts[watch['processor']] }}</span>
|
||||
{%- endif -%}
|
||||
{%- for watch_tag_uuid, watch_tag in datastore.get_all_tags_for_watch(watch['uuid']).items() -%}
|
||||
<span class="watch-tag-list">{{ watch_tag.title }}</span>
|
||||
<span class="watch-tag-list tag-{{ watch_tag.title|sanitize_tag_class }}">{{ watch_tag.title }}</span>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
<div class="status-icons">
|
||||
|
||||
@@ -297,6 +297,25 @@ def _jinja2_filter_fetcher_status_icons(fetcher_name):
|
||||
|
||||
return ''
|
||||
|
||||
@app.template_filter('sanitize_tag_class')
|
||||
def _jinja2_filter_sanitize_tag_class(tag_title):
|
||||
"""Sanitize a tag title to create a valid CSS class name.
|
||||
Removes all non-alphanumeric characters and converts to lowercase.
|
||||
|
||||
Args:
|
||||
tag_title: The tag title string
|
||||
|
||||
Returns:
|
||||
str: A sanitized string suitable for use as a CSS class name
|
||||
"""
|
||||
import re
|
||||
# Remove all non-alphanumeric characters and convert to lowercase
|
||||
sanitized = re.sub(r'[^a-zA-Z0-9]', '', tag_title).lower()
|
||||
# Ensure it starts with a letter (CSS requirement)
|
||||
if sanitized and not sanitized[0].isalpha():
|
||||
sanitized = 'tag' + sanitized
|
||||
return sanitized if sanitized else 'tag'
|
||||
|
||||
# Import login_optionally_required from auth_decorator
|
||||
from changedetectionio.auth_decorator import login_optionally_required
|
||||
|
||||
@@ -895,7 +914,7 @@ def notification_runner():
|
||||
# At the moment only one thread runs (single runner)
|
||||
n_object = notification_q.get(block=False)
|
||||
except queue.Empty:
|
||||
time.sleep(1)
|
||||
app.config.exit.wait(1)
|
||||
|
||||
else:
|
||||
|
||||
@@ -932,7 +951,7 @@ def notification_runner():
|
||||
app.config['watch_check_update_SIGNAL'].send(app_context=app, watch_uuid=n_object.get('uuid'))
|
||||
|
||||
# Process notifications
|
||||
notification_debug_log+= ["{} - SENDING - {}".format(now.strftime("%Y/%m/%d %H:%M:%S,000"), json.dumps(sent_obj))]
|
||||
notification_debug_log+= ["{} - SENDING - {}".format(now.strftime("%c"), json.dumps(sent_obj))]
|
||||
# Trim the log length
|
||||
notification_debug_log = notification_debug_log[-100:]
|
||||
|
||||
@@ -990,7 +1009,7 @@ def ticker_thread_check_time_launch_checks():
|
||||
# Re #438 - Don't place more watches in the queue to be checked if the queue is already large
|
||||
while update_q.qsize() >= 2000:
|
||||
logger.warning(f"Recheck watches queue size limit reached ({MAX_QUEUE_SIZE}), skipping adding more items")
|
||||
time.sleep(3)
|
||||
app.config.exit.wait(10.0)
|
||||
|
||||
|
||||
recheck_time_system_seconds = int(datastore.threshold_seconds)
|
||||
@@ -1088,8 +1107,5 @@ def ticker_thread_check_time_launch_checks():
|
||||
# Reset for next time
|
||||
watch.jitter_seconds = 0
|
||||
|
||||
# Wait before checking the list again - saves CPU
|
||||
time.sleep(1)
|
||||
|
||||
# Should be low so we can break this out in testing
|
||||
app.config.exit.wait(1)
|
||||
|
||||
@@ -781,8 +781,8 @@ class SingleBrowserStep(Form):
|
||||
|
||||
class processor_text_json_diff_form(commonSettingsForm):
|
||||
|
||||
url = fields.URLField('URL', validators=[validateURL()])
|
||||
tags = StringTagUUID('Group tag', [validators.Optional()], default='')
|
||||
url = fields.URLField('Web Page URL', validators=[validateURL()])
|
||||
tags = StringTagUUID('Group Tag', [validators.Optional()], default='')
|
||||
|
||||
time_between_check = EnhancedFormField(
|
||||
TimeBetweenCheckForm,
|
||||
|
||||
@@ -34,13 +34,16 @@ def get_timeago_locale(flask_locale):
|
||||
'no': 'nb_NO', # Norwegian Bokmål
|
||||
'hi': 'in_HI', # Hindi
|
||||
'cs': 'en', # Czech not supported by timeago, fallback to English
|
||||
'en_GB': 'en', # British English - timeago uses 'en'
|
||||
'en_US': 'en', # American English - timeago uses 'en'
|
||||
}
|
||||
return locale_map.get(flask_locale, flask_locale)
|
||||
|
||||
# Language metadata: flag icon CSS class and native name
|
||||
# Using flag-icons library: https://flagicons.lipis.dev/
|
||||
LANGUAGE_DATA = {
|
||||
'en': {'flag': 'fi fi-gb fis', 'name': 'English'},
|
||||
'en_GB': {'flag': 'fi fi-gb fis', 'name': 'English (UK)'},
|
||||
'en_US': {'flag': 'fi fi-us fis', 'name': 'English (US)'},
|
||||
'de': {'flag': 'fi fi-de fis', 'name': 'Deutsch'},
|
||||
'fr': {'flag': 'fi fi-fr fis', 'name': 'Français'},
|
||||
'ko': {'flag': 'fi fi-kr fis', 'name': '한국어'},
|
||||
@@ -71,10 +74,7 @@ def get_available_languages():
|
||||
"""
|
||||
translations_dir = Path(__file__).parent / 'translations'
|
||||
|
||||
# Always include English as base language
|
||||
available = {
|
||||
'en': LANGUAGE_DATA['en']
|
||||
}
|
||||
available = {}
|
||||
|
||||
# Scan for translation directories
|
||||
if translations_dir.exists():
|
||||
@@ -85,6 +85,10 @@ def get_available_languages():
|
||||
if po_file.exists():
|
||||
available[lang_dir.name] = LANGUAGE_DATA[lang_dir.name]
|
||||
|
||||
# If no English variants found, fall back to adding en_GB as default
|
||||
if 'en_GB' not in available and 'en_US' not in available:
|
||||
available['en_GB'] = LANGUAGE_DATA['en_GB']
|
||||
|
||||
return available
|
||||
|
||||
|
||||
|
||||
99
changedetectionio/static/js/flask-toast-bridge.js
Normal file
99
changedetectionio/static/js/flask-toast-bridge.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Flask Toast Bridge
|
||||
* Automatically converts Flask flash messages to toast notifications
|
||||
*
|
||||
* Maps Flask message categories to toast types:
|
||||
* - 'message' or 'info' -> info toast
|
||||
* - 'success' -> success toast
|
||||
* - 'error' or 'danger' -> error toast
|
||||
* - 'warning' -> warning toast
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Find the Flask messages container
|
||||
const messagesContainer = document.querySelector('ul.messages');
|
||||
|
||||
if (!messagesContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all flash messages
|
||||
const messages = messagesContainer.querySelectorAll('li');
|
||||
|
||||
if (messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let toastIndex = 0;
|
||||
|
||||
// Convert each message to a toast (except errors)
|
||||
messages.forEach(function(messageEl) {
|
||||
const text = messageEl.textContent.trim();
|
||||
const category = getMessageCategory(messageEl);
|
||||
|
||||
// Skip error messages - they should stay in the page
|
||||
if (category === 'error') {
|
||||
return;
|
||||
}
|
||||
|
||||
const toastType = mapCategoryToToastType(category);
|
||||
|
||||
// Stagger toast appearance for multiple messages
|
||||
setTimeout(function() {
|
||||
Toast[toastType](text, {
|
||||
duration: 6000 // 6 seconds for Flask messages
|
||||
});
|
||||
}, toastIndex * 200); // 200ms delay between each toast
|
||||
|
||||
toastIndex++;
|
||||
|
||||
// Hide this specific message element (not errors)
|
||||
messageEl.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Extract message category from class names
|
||||
*/
|
||||
function getMessageCategory(messageEl) {
|
||||
const classes = messageEl.className.split(' ');
|
||||
|
||||
// Common Flask flash message categories
|
||||
const categoryMap = {
|
||||
'success': 'success',
|
||||
'error': 'error',
|
||||
'danger': 'error',
|
||||
'warning': 'warning',
|
||||
'info': 'info',
|
||||
'message': 'info',
|
||||
'notice': 'info'
|
||||
};
|
||||
|
||||
for (let className of classes) {
|
||||
if (categoryMap[className]) {
|
||||
return categoryMap[className];
|
||||
}
|
||||
}
|
||||
|
||||
// Default to info if no category found
|
||||
return 'info';
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Flask category to Toast type
|
||||
*/
|
||||
function mapCategoryToToastType(category) {
|
||||
const typeMap = {
|
||||
'success': 'success',
|
||||
'error': 'error',
|
||||
'warning': 'warning',
|
||||
'info': 'info'
|
||||
};
|
||||
|
||||
return typeMap[category] || 'info';
|
||||
}
|
||||
|
||||
})();
|
||||
69
changedetectionio/static/js/hamburger-menu.js
Normal file
69
changedetectionio/static/js/hamburger-menu.js
Normal file
@@ -0,0 +1,69 @@
|
||||
// Hamburger menu toggle functionality
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const hamburgerToggle = document.getElementById('hamburger-toggle');
|
||||
const mobileMenuDrawer = document.getElementById('mobile-menu-drawer');
|
||||
const mobileMenuOverlay = document.getElementById('mobile-menu-overlay');
|
||||
|
||||
if (!hamburgerToggle || !mobileMenuDrawer || !mobileMenuOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
hamburgerToggle.classList.add('active');
|
||||
mobileMenuDrawer.classList.add('active');
|
||||
mobileMenuOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
hamburgerToggle.classList.remove('active');
|
||||
mobileMenuDrawer.classList.remove('active');
|
||||
mobileMenuOverlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
if (mobileMenuDrawer.classList.contains('active')) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle menu on hamburger click
|
||||
hamburgerToggle.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
toggleMenu();
|
||||
});
|
||||
|
||||
// Close menu when clicking overlay
|
||||
mobileMenuOverlay.addEventListener('click', closeMenu);
|
||||
|
||||
// Close menu when clicking a menu item
|
||||
const menuItems = mobileMenuDrawer.querySelectorAll('.mobile-menu-items a');
|
||||
menuItems.forEach(function(item) {
|
||||
item.addEventListener('click', closeMenu);
|
||||
});
|
||||
|
||||
// Close menu on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenuDrawer.classList.contains('active')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu when window is resized above mobile breakpoint
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function() {
|
||||
if (window.innerWidth > 768 && mobileMenuDrawer.classList.contains('active')) {
|
||||
closeMenu();
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -3,17 +3,17 @@
|
||||
* Allows users to select their preferred language
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const languageButton = document.getElementById('language-selector');
|
||||
const languageModal = document.getElementById('language-modal');
|
||||
const closeButton = document.getElementById('close-language-modal');
|
||||
$(document).ready(function() {
|
||||
const $languageButton = $('.language-selector');
|
||||
const $languageModal = $('#language-modal');
|
||||
const $closeButton = $('#close-language-modal');
|
||||
|
||||
if (!languageButton || !languageModal) {
|
||||
if (!$languageButton.length || !$languageModal.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open modal when language button is clicked
|
||||
languageButton.addEventListener('click', function(e) {
|
||||
$languageButton.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Update all language links to include current hash in the redirect parameter
|
||||
@@ -21,51 +21,53 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const currentHash = window.location.hash;
|
||||
|
||||
if (currentHash) {
|
||||
const languageOptions = languageModal.querySelectorAll('.language-option');
|
||||
languageOptions.forEach(function(option) {
|
||||
const url = new URL(option.href, window.location.origin);
|
||||
const $languageOptions = $languageModal.find('.language-option');
|
||||
$languageOptions.each(function() {
|
||||
const $option = $(this);
|
||||
const url = new URL($option.attr('href'), window.location.origin);
|
||||
// Update the redirect parameter to include the hash
|
||||
const redirectPath = currentPath + currentHash;
|
||||
url.searchParams.set('redirect', redirectPath);
|
||||
option.setAttribute('href', url.pathname + url.search + url.hash);
|
||||
$option.attr('href', url.pathname + url.search + url.hash);
|
||||
});
|
||||
}
|
||||
|
||||
languageModal.showModal();
|
||||
$languageModal[0].showModal();
|
||||
});
|
||||
|
||||
// Close modal when cancel button is clicked
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', function() {
|
||||
languageModal.close();
|
||||
if ($closeButton.length) {
|
||||
$closeButton.on('click', function() {
|
||||
$languageModal[0].close();
|
||||
});
|
||||
}
|
||||
|
||||
// Close modal when clicking outside (on backdrop)
|
||||
languageModal.addEventListener('click', function(e) {
|
||||
const rect = languageModal.getBoundingClientRect();
|
||||
$languageModal.on('click', function(e) {
|
||||
const rect = this.getBoundingClientRect();
|
||||
if (
|
||||
e.clientY < rect.top ||
|
||||
e.clientY > rect.bottom ||
|
||||
e.clientX < rect.left ||
|
||||
e.clientX > rect.right
|
||||
) {
|
||||
languageModal.close();
|
||||
$languageModal[0].close();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on Escape key
|
||||
languageModal.addEventListener('cancel', function(e) {
|
||||
$languageModal.on('cancel', function(e) {
|
||||
e.preventDefault();
|
||||
languageModal.close();
|
||||
$languageModal[0].close();
|
||||
});
|
||||
|
||||
// Highlight current language
|
||||
const currentLocale = document.documentElement.lang || 'en';
|
||||
const languageOptions = languageModal.querySelectorAll('.language-option');
|
||||
languageOptions.forEach(function(option) {
|
||||
if (option.dataset.locale === currentLocale) {
|
||||
option.classList.add('active');
|
||||
const currentLocale = $('html').attr('lang') || 'en';
|
||||
const $languageOptions = $languageModal.find('.language-option');
|
||||
$languageOptions.each(function() {
|
||||
const $option = $(this);
|
||||
if ($option.attr('data-locale') === currentLocale) {
|
||||
$option.addClass('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,6 +74,9 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
|
||||
// Cache DOM elements for performance
|
||||
const queueBubble = document.getElementById('queue-bubble');
|
||||
|
||||
// Only try to connect if authentication isn't required or user is authenticated
|
||||
// The 'is_authenticated' variable will be set in the template
|
||||
if (typeof is_authenticated !== 'undefined' ? is_authenticated : true) {
|
||||
@@ -115,7 +118,40 @@ $(document).ready(function () {
|
||||
|
||||
socket.on('queue_size', function (data) {
|
||||
console.log(`${data.event_timestamp} - Queue size update: ${data.q_length}`);
|
||||
// Update queue size display if implemented in the UI
|
||||
|
||||
// Update queue bubble in action sidebar
|
||||
//if (queueBubble) {
|
||||
if (0) {
|
||||
const count = parseInt(data.q_length) || 0;
|
||||
const oldCount = parseInt(queueBubble.getAttribute('data-count')) || 0;
|
||||
|
||||
if (count > 0) {
|
||||
// Format number according to browser locale
|
||||
const formatter = new Intl.NumberFormat(navigator.language);
|
||||
queueBubble.textContent = formatter.format(count);
|
||||
queueBubble.setAttribute('data-count', count);
|
||||
queueBubble.classList.add('visible');
|
||||
|
||||
// Add large-number class for numbers > 999
|
||||
if (count > 999) {
|
||||
queueBubble.classList.add('large-number');
|
||||
} else {
|
||||
queueBubble.classList.remove('large-number');
|
||||
}
|
||||
|
||||
// Pulse animation if count changed
|
||||
if (count !== oldCount) {
|
||||
queueBubble.classList.remove('pulse');
|
||||
// Force reflow to restart animation
|
||||
void queueBubble.offsetWidth;
|
||||
queueBubble.classList.add('pulse');
|
||||
}
|
||||
} else {
|
||||
// Hide bubble when queue is empty
|
||||
queueBubble.classList.remove('visible', 'pulse', 'large-number');
|
||||
queueBubble.setAttribute('data-count', '0');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Listen for operation results
|
||||
|
||||
96
changedetectionio/static/js/search-modal.js
Normal file
96
changedetectionio/static/js/search-modal.js
Normal file
@@ -0,0 +1,96 @@
|
||||
// Search modal functionality
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const searchModal = document.getElementById('search-modal');
|
||||
const openSearchButton = document.getElementById('open-search-modal');
|
||||
const closeSearchButton = document.getElementById('close-search-modal');
|
||||
const searchForm = document.getElementById('search-form');
|
||||
const searchInput = document.getElementById('search-modal-input');
|
||||
|
||||
if (!searchModal || !openSearchButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open modal
|
||||
function openSearchModal() {
|
||||
searchModal.showModal();
|
||||
// Focus the input after a small delay to ensure modal is rendered
|
||||
setTimeout(function() {
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Close modal
|
||||
function closeSearchModal() {
|
||||
searchModal.close();
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Open search modal on button click
|
||||
openSearchButton.addEventListener('click', openSearchModal);
|
||||
|
||||
// Close modal on cancel button
|
||||
if (closeSearchButton) {
|
||||
closeSearchButton.addEventListener('click', closeSearchModal);
|
||||
}
|
||||
|
||||
// Close modal on escape key (native behavior for dialog)
|
||||
searchModal.addEventListener('cancel', function(e) {
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal when clicking the backdrop
|
||||
searchModal.addEventListener('click', function(e) {
|
||||
const rect = searchModal.getBoundingClientRect();
|
||||
const isInDialog = (
|
||||
rect.top <= e.clientY &&
|
||||
e.clientY <= rect.top + rect.height &&
|
||||
rect.left <= e.clientX &&
|
||||
e.clientX <= rect.left + rect.width
|
||||
);
|
||||
if (!isInDialog) {
|
||||
closeSearchModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Alt+S keyboard shortcut
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.altKey && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault();
|
||||
openSearchModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
if (searchForm) {
|
||||
searchForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(searchForm);
|
||||
const searchQuery = formData.get('q');
|
||||
const tags = formData.get('tags');
|
||||
|
||||
// Build URL
|
||||
const params = new URLSearchParams();
|
||||
if (searchQuery) {
|
||||
params.append('q', searchQuery);
|
||||
}
|
||||
if (tags) {
|
||||
params.append('tags', tags);
|
||||
}
|
||||
|
||||
// Navigate to search results
|
||||
window.location.href = '?' + params.toString();
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,11 +1,12 @@
|
||||
// Rewrite this is a plugin.. is all this JS really 'worth it?'
|
||||
|
||||
window.addEventListener('hashchange', function () {
|
||||
var tabs = document.getElementsByClassName('active');
|
||||
while (tabs[0]) {
|
||||
tabs[0].classList.remove('active');
|
||||
document.body.classList.remove('full-width');
|
||||
}
|
||||
// Only remove active from tab elements, not menu items
|
||||
var tabs = document.querySelectorAll('.tabs li.active');
|
||||
tabs.forEach(function(tab) {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
document.body.classList.remove('full-width');
|
||||
set_active_tab();
|
||||
}, false);
|
||||
|
||||
@@ -22,9 +23,9 @@ if (!has_errors.length) {
|
||||
|
||||
function set_active_tab() {
|
||||
document.body.classList.remove('full-width');
|
||||
var tab = document.querySelectorAll("a[href='" + location.hash + "']");
|
||||
var tab = document.querySelectorAll(".tabs a[href='" + location.hash + "']");
|
||||
if (tab.length) {
|
||||
tab[0].parentElement.className = "active";
|
||||
tab[0].parentElement.classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
275
changedetectionio/static/js/toast.js
Normal file
275
changedetectionio/static/js/toast.js
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* Toast - Modern toast notification system
|
||||
* Inspired by Toastify, Notyf, and React Hot Toast
|
||||
*
|
||||
* Usage:
|
||||
* Toast.success('Operation completed!');
|
||||
* Toast.error('Something went wrong');
|
||||
* Toast.info('Here is some information');
|
||||
* Toast.warning('Warning message');
|
||||
* Toast.show('Custom message', { type: 'success', duration: 3000 });
|
||||
*
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
// Toast configuration
|
||||
const defaultConfig = {
|
||||
duration: 5000, // Auto-dismiss after 5 seconds (0 = no auto-dismiss)
|
||||
position: 'top-center', // top-right, top-center, top-left, bottom-right, bottom-center, bottom-left
|
||||
closeButton: true, // Show close button
|
||||
progressBar: true, // Show progress bar
|
||||
pauseOnHover: true, // Pause auto-dismiss on hover
|
||||
maxToasts: 5, // Maximum toasts to show at once
|
||||
offset: '20px', // Offset from edge
|
||||
zIndex: 10000, // Z-index for toast container
|
||||
};
|
||||
|
||||
let config = { ...defaultConfig };
|
||||
let toastCount = 0;
|
||||
let container = null;
|
||||
|
||||
/**
|
||||
* Initialize toast system with custom config
|
||||
*/
|
||||
function init(userConfig = {}) {
|
||||
config = { ...defaultConfig, ...userConfig };
|
||||
createContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create toast container if it doesn't exist
|
||||
*/
|
||||
function createContainer() {
|
||||
if (container) return;
|
||||
|
||||
container = document.createElement('div');
|
||||
container.className = `toast-container toast-${config.position}`;
|
||||
container.style.zIndex = config.zIndex;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a toast notification
|
||||
*/
|
||||
function show(message, options = {}) {
|
||||
createContainer();
|
||||
|
||||
const toast = createToastElement(message, options);
|
||||
|
||||
// Limit number of toasts
|
||||
const existingToasts = container.querySelectorAll('.toast');
|
||||
if (existingToasts.length >= config.maxToasts) {
|
||||
removeToast(existingToasts[0]);
|
||||
}
|
||||
|
||||
// Add to container
|
||||
container.appendChild(toast);
|
||||
|
||||
// Trigger animation
|
||||
requestAnimationFrame(() => {
|
||||
toast.classList.add('toast-show');
|
||||
});
|
||||
|
||||
// Auto-dismiss
|
||||
if (options.duration !== 0 && (options.duration || config.duration) > 0) {
|
||||
setupAutoDismiss(toast, options.duration || config.duration);
|
||||
}
|
||||
|
||||
return {
|
||||
dismiss: () => removeToast(toast)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create toast DOM element
|
||||
*/
|
||||
function createToastElement(message, options) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${options.type || 'default'}`;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'polite');
|
||||
|
||||
// Icon
|
||||
const icon = createIcon(options.type || 'default');
|
||||
if (icon) {
|
||||
toast.appendChild(icon);
|
||||
}
|
||||
|
||||
// Message
|
||||
const messageEl = document.createElement('div');
|
||||
messageEl.className = 'toast-message';
|
||||
messageEl.textContent = message;
|
||||
toast.appendChild(messageEl);
|
||||
|
||||
// Close button
|
||||
if (options.closeButton !== false && config.closeButton) {
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'toast-close';
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.setAttribute('aria-label', 'Close');
|
||||
closeBtn.onclick = () => removeToast(toast);
|
||||
toast.appendChild(closeBtn);
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
if (options.progressBar !== false && config.progressBar && (options.duration || config.duration) > 0) {
|
||||
const progressBar = document.createElement('div');
|
||||
progressBar.className = 'toast-progress';
|
||||
toast.appendChild(progressBar);
|
||||
toast._progressBar = progressBar;
|
||||
}
|
||||
|
||||
return toast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create icon based on toast type
|
||||
*/
|
||||
function createIcon(type) {
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.className = 'toast-icon';
|
||||
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('fill', 'none');
|
||||
svg.setAttribute('stroke', 'currentColor');
|
||||
svg.setAttribute('stroke-width', '2');
|
||||
|
||||
let path = '';
|
||||
switch (type) {
|
||||
case 'success':
|
||||
path = 'M20 6L9 17l-5-5';
|
||||
break;
|
||||
case 'error':
|
||||
path = 'M18 6L6 18M6 6l12 12';
|
||||
break;
|
||||
case 'warning':
|
||||
path = 'M12 9v4m0 4h.01M12 2a10 10 0 100 20 10 10 0 000-20z';
|
||||
svg.setAttribute('stroke-width', '1.5');
|
||||
break;
|
||||
case 'info':
|
||||
path = 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
||||
svg.setAttribute('stroke-width', '1.5');
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
pathEl.setAttribute('d', path);
|
||||
pathEl.setAttribute('stroke-linecap', 'round');
|
||||
pathEl.setAttribute('stroke-linejoin', 'round');
|
||||
svg.appendChild(pathEl);
|
||||
iconEl.appendChild(svg);
|
||||
|
||||
return iconEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup auto-dismiss with progress bar
|
||||
*/
|
||||
function setupAutoDismiss(toast, duration) {
|
||||
let startTime = Date.now();
|
||||
let remainingTime = duration;
|
||||
let isPaused = false;
|
||||
let animationFrame;
|
||||
|
||||
function updateProgress() {
|
||||
if (isPaused) return;
|
||||
|
||||
const elapsed = Date.now() - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
if (toast._progressBar) {
|
||||
toast._progressBar.style.transform = `scaleX(${1 - progress})`;
|
||||
}
|
||||
|
||||
if (progress >= 1) {
|
||||
removeToast(toast);
|
||||
} else {
|
||||
animationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
}
|
||||
|
||||
// Pause on hover
|
||||
if (config.pauseOnHover) {
|
||||
toast.addEventListener('mouseenter', () => {
|
||||
isPaused = true;
|
||||
remainingTime = duration - (Date.now() - startTime);
|
||||
cancelAnimationFrame(animationFrame);
|
||||
});
|
||||
|
||||
toast.addEventListener('mouseleave', () => {
|
||||
isPaused = false;
|
||||
startTime = Date.now();
|
||||
duration = remainingTime;
|
||||
animationFrame = requestAnimationFrame(updateProgress);
|
||||
});
|
||||
}
|
||||
|
||||
animationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove toast with animation
|
||||
*/
|
||||
function removeToast(toast) {
|
||||
if (!toast || !toast.parentElement) return;
|
||||
|
||||
toast.classList.add('toast-hide');
|
||||
|
||||
// Remove after animation
|
||||
setTimeout(() => {
|
||||
if (toast.parentElement) {
|
||||
toast.parentElement.removeChild(toast);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Convenience methods
|
||||
function success(message, options = {}) {
|
||||
return show(message, { ...options, type: 'success' });
|
||||
}
|
||||
|
||||
function error(message, options = {}) {
|
||||
return show(message, { ...options, type: 'error' });
|
||||
}
|
||||
|
||||
function warning(message, options = {}) {
|
||||
return show(message, { ...options, type: 'warning' });
|
||||
}
|
||||
|
||||
function info(message, options = {}) {
|
||||
return show(message, { ...options, type: 'info' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all toasts
|
||||
*/
|
||||
function clear() {
|
||||
if (!container) return;
|
||||
const toasts = container.querySelectorAll('.toast');
|
||||
toasts.forEach(removeToast);
|
||||
}
|
||||
|
||||
// Public API
|
||||
window.Toast = {
|
||||
init,
|
||||
show,
|
||||
success,
|
||||
error,
|
||||
warning,
|
||||
info,
|
||||
clear,
|
||||
version: '1.0.0'
|
||||
};
|
||||
|
||||
// Auto-initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init();
|
||||
});
|
||||
|
||||
})(window);
|
||||
15
changedetectionio/static/js/toastify.min.css
vendored
Normal file
15
changedetectionio/static/js/toastify.min.css
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Minified by jsDelivr using clean-css v5.3.3.
|
||||
* Original file: /npm/toastify-js@1.12.0/src/toastify.css
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
/*!
|
||||
* Toastify js 1.12.0
|
||||
* https://github.com/apvarun/toastify-js
|
||||
* @license MIT licensed
|
||||
*
|
||||
* Copyright (C) 2018 Varun A P
|
||||
*/
|
||||
.toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215, .61, .355, 1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px);z-index:2147483647}.toastify.on{opacity:1}.toast-close{background:0 0;border:0;color:#fff;cursor:pointer;font-family:inherit;font-size:1em;opacity:.4;padding:0 5px}.toastify-right{right:15px}.toastify-left{left:15px}.toastify-top{top:-150px}.toastify-bottom{bottom:-150px}.toastify-rounded{border-radius:25px}.toastify-avatar{width:1.5em;height:1.5em;margin:-7px 5px;border-radius:2px}.toastify-center{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content;max-width:-moz-fit-content}@media only screen and (max-width:360px){.toastify-left,.toastify-right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}}
|
||||
/*# sourceMappingURL=/sm/cb4335d1b03e933ed85cb59fffa60cf51f07567ed09831438c60f59afd166464.map */
|
||||
15
changedetectionio/static/js/toastify.min.js
vendored
Normal file
15
changedetectionio/static/js/toastify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -3,14 +3,12 @@
|
||||
* Toggles theme between light and dark mode.
|
||||
*/
|
||||
$(document).ready(function () {
|
||||
const button = document.getElementById("toggle-light-mode");
|
||||
|
||||
button.onclick = () => {
|
||||
const htmlElement = document.getElementsByTagName("html");
|
||||
const isDarkMode = htmlElement[0].dataset.darkmode === "true";
|
||||
htmlElement[0].dataset.darkmode = !isDarkMode;
|
||||
setCookieValue(!isDarkMode);
|
||||
};
|
||||
$(".toggle-light-mode").on("click", function () {
|
||||
const isDark = $("html").attr("data-darkmode") === "true";
|
||||
$("html").attr("data-darkmode", !isDark);
|
||||
setCookieValue(!isDark);
|
||||
});
|
||||
|
||||
const setCookieValue = (value) => {
|
||||
document.cookie = `css_dark_mode=${value};max-age=31536000;path=/`
|
||||
|
||||
7
changedetectionio/static/styles/scss/_settings.scss
Normal file
7
changedetectionio/static/styles/scss/_settings.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* SCSS variables (compile-time)
|
||||
* These can be used in media queries and other places where CSS custom properties don't work
|
||||
*/
|
||||
|
||||
// Breakpoints
|
||||
$desktop-wide-breakpoint: 980px;
|
||||
115
changedetectionio/static/styles/scss/parts/_action_sidebar.scss
Normal file
115
changedetectionio/static/styles/scss/parts/_action_sidebar.scss
Normal file
@@ -0,0 +1,115 @@
|
||||
// Action Sidebar - Minimal navigation icons with light grey aesthetic
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.action-sidebar {
|
||||
position: sticky;
|
||||
top: 100px;
|
||||
flex-shrink: 0;
|
||||
width: 80px;
|
||||
height: fit-content;
|
||||
background: transparent;
|
||||
padding: 1.5rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
z-index: 0;
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
position: relative;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
padding: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.action-sidebar-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
min-width: 64px;
|
||||
text-decoration: none;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
|
||||
.action-icon {
|
||||
stroke: #fff;
|
||||
stroke-width: 2.5;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
stroke: #fff;
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
transition: stroke 0.2s ease;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
color: #fff;
|
||||
transition: color 0.2s ease;
|
||||
max-width: 60px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.content-main {
|
||||
flex: 0 1 auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Dark mode adjustments
|
||||
html[data-darkmode=true] {
|
||||
.action-icon {
|
||||
/* stroke: #666;*/
|
||||
}
|
||||
|
||||
.action-label {
|
||||
/* color: #666;*/
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
|
||||
#toggle-light-mode {
|
||||
/* width: 3rem;*/
|
||||
.toggle-light-mode {
|
||||
/* default */
|
||||
.icon-dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
html[data-darkmode="true"] {
|
||||
#toggle-light-mode {
|
||||
.toggle-light-mode {
|
||||
.icon-light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
167
changedetectionio/static/styles/scss/parts/_hamburger_menu.scss
Normal file
167
changedetectionio/static/styles/scss/parts/_hamburger_menu.scss
Normal file
@@ -0,0 +1,167 @@
|
||||
// Hamburger Menu for Mobile Navigation
|
||||
@use "../settings" as *;
|
||||
|
||||
.hamburger-menu {
|
||||
display: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
z-index: 10001;
|
||||
position: relative;
|
||||
|
||||
@media only screen and (max-width: $desktop-wide-breakpoint) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger-icon {
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
background: var(--color-text);
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger-menu.active {
|
||||
.hamburger-icon span:nth-child(1) {
|
||||
transform: translateY(8.5px) rotate(45deg);
|
||||
}
|
||||
|
||||
.hamburger-icon span:nth-child(2) {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
.hamburger-icon span:nth-child(3) {
|
||||
transform: translateY(-8.5px) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile menu overlay
|
||||
.mobile-menu-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile menu drawer
|
||||
.mobile-menu-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -280px;
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
background: var(--color-background);
|
||||
opacity: 1;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10000;
|
||||
transition: right 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
overflow-y: auto;
|
||||
padding-top: 60px;
|
||||
|
||||
&.active {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mobile-menu-items {
|
||||
list-style: none;
|
||||
padding: 1rem 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid var(--color-border-table-cell);
|
||||
|
||||
>* {
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-menu-link-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logo styling
|
||||
.logo-cdio {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
|
||||
.logo-cd {
|
||||
color: var(--color-grey-500);
|
||||
}
|
||||
|
||||
.logo-io {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
// Always visible items container
|
||||
.menu-always-visible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
// Hide regular menu items on mobile (but not in mobile drawer)
|
||||
@media only screen and (max-width: $desktop-wide-breakpoint) {
|
||||
#top-right-menu .menu-collapsible {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.pure-menu-horizontal {
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
|
||||
#nav-menu {
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop - hide mobile menu elements
|
||||
@media only screen and (min-width: 1025px) {
|
||||
.hamburger-menu,
|
||||
.mobile-menu-drawer,
|
||||
.mobile-menu-overlay {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-darkmode=true] {
|
||||
.mobile-menu-drawer {
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
69
changedetectionio/static/styles/scss/parts/_language.scss
Normal file
69
changedetectionio/static/styles/scss/parts/_language.scss
Normal file
@@ -0,0 +1,69 @@
|
||||
#language-selector-flag {
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Language Selector Modal Styles
|
||||
.language-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.language-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-menu-link-hover);
|
||||
border-color: var(--color-border-table-cell);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-link);
|
||||
color: var(--color-text-button);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.language-name {
|
||||
flex-grow: 1;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.language-modal {
|
||||
.language-list {
|
||||
.lang-option {
|
||||
display: inline-block;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.5em;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
233
changedetectionio/static/styles/scss/parts/_login_form.scss
Normal file
233
changedetectionio/static/styles/scss/parts/_login_form.scss
Normal file
@@ -0,0 +1,233 @@
|
||||
// Modern Login Form - Friendly and Welcoming Design
|
||||
|
||||
.login-form {
|
||||
min-height: 52vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
|
||||
.inner {
|
||||
background: var(--color-background);
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 10px 40px rgba(0, 0, 0, 0.08),
|
||||
0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
padding: 3rem 2.5rem;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
|
||||
box-shadow:
|
||||
0 15px 50px rgba(0, 0, 0, 0.12),
|
||||
0 5px 15px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pure-control-group {
|
||||
margin-bottom: 1.75rem;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 0.875rem 1rem;
|
||||
border: 2px solid var(--color-grey-800);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
background: var(--color-background-input);
|
||||
color: var(--color-text-input);
|
||||
transition: all 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-link);
|
||||
box-shadow: 0 0 0 3px rgba(27, 152, 248, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-input-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
width: 100%;
|
||||
padding: 0.875rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: var(--color-background-button-primary);
|
||||
color: var(--color-text-button);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(27, 152, 248, 0.2);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(27, 152, 248, 0.3);
|
||||
background: #0066cc;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(27, 152, 248, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Messages styling for login page
|
||||
.content-main > ul.messages {
|
||||
position: fixed;
|
||||
top: 120px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: 1000;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
|
||||
li {
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: slideDown 0.3s ease-out;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&.error {
|
||||
background: #fee;
|
||||
border: 2px solid #ef4444;
|
||||
color: #991b1b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background: #f0fdf4;
|
||||
border: 2px solid #10b981;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
&.info,
|
||||
&.message {
|
||||
background: #eff6ff;
|
||||
border: 2px solid #3b82f6;
|
||||
color: #1e40af;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode adjustments
|
||||
html[data-darkmode="true"] {
|
||||
.login-form {
|
||||
.inner {
|
||||
box-shadow:
|
||||
0 10px 40px rgba(0, 0, 0, 0.4),
|
||||
0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&:hover {
|
||||
box-shadow:
|
||||
0 15px 50px rgba(0, 0, 0, 0.5),
|
||||
0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="password"] {
|
||||
border-color: var(--color-grey-400);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-main > ul.messages {
|
||||
li {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
|
||||
&.error {
|
||||
background: #4a1d1d;
|
||||
border-color: #ef4444;
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background: #1a3a2a;
|
||||
border-color: #10b981;
|
||||
color: #86efac;
|
||||
}
|
||||
|
||||
&.info,
|
||||
&.message {
|
||||
background: #1e3a5f;
|
||||
border-color: #3b82f6;
|
||||
color: #93c5fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile adjustments
|
||||
@media only screen and (max-width: 768px) {
|
||||
.login-form {
|
||||
min-height: auto;
|
||||
padding: 1rem 0.5rem;
|
||||
padding-top: 5rem; // Space for error message
|
||||
|
||||
.inner {
|
||||
padding: 2rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-main > ul.messages {
|
||||
top: 70px; // Higher up on mobile to avoid overlap
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
transform: none;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,22 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// Active menu item styling
|
||||
&.active {
|
||||
.pure-menu-link {
|
||||
background-color: var(--color-background-menu-link-hover);
|
||||
color: var(--color-text-menu-link-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#cdio-logo {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
#inline-menu-extras-group {
|
||||
>* {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Reusable notification bubble for action sidebar icons
|
||||
|
||||
.action-sidebar-item {
|
||||
position: relative;
|
||||
|
||||
.notification-bubble {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
background: #ff4444;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
border-radius: 9px;
|
||||
padding: 0 2px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
pointer-events: none;
|
||||
transition: all 0.2s ease;
|
||||
display: none;
|
||||
|
||||
// Red bubble for errors/urgent
|
||||
&.red-bubble {
|
||||
background: #ff4444;
|
||||
}
|
||||
|
||||
// Blue bubble for informational
|
||||
&.blue-bubble {
|
||||
background: #4a9eff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Pulse animation when value changes
|
||||
&.pulse {
|
||||
animation: bubblePulse 0.4s ease-out;
|
||||
}
|
||||
|
||||
// Large numbers get smaller font
|
||||
&.large-number {
|
||||
font-size: 8px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bubblePulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode adjustments
|
||||
html[data-darkmode=true] {
|
||||
.notification-bubble {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Search Modal Styles
|
||||
|
||||
#search-modal {
|
||||
.modal-body {
|
||||
padding: 2rem 1.5rem;
|
||||
|
||||
.pure-control-group {
|
||||
padding-bottom: 0;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
#search-modal-input {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.6rem 0.8rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid var(--color-border-input);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-background-input);
|
||||
color: var(--color-text-input);
|
||||
box-shadow: inset 0 1px 3px var(--color-shadow-input);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-link);
|
||||
box-shadow: 0 0 0 3px rgba(27, 152, 248, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-input-placeholder);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode adjustments
|
||||
html[data-darkmode=true] {
|
||||
#search-modal {
|
||||
#search-modal-input {
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px rgba(89, 189, 251, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
changedetectionio/static/styles/scss/parts/_tabs.scss
Normal file
57
changedetectionio/static/styles/scss/parts/_tabs.scss
Normal file
@@ -0,0 +1,57 @@
|
||||
body.wrapped-tabs {
|
||||
.tabs {
|
||||
ul {
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--tab-width, 180px), 1fr));
|
||||
grid-auto-flow: row;
|
||||
grid-auto-columns: unset;
|
||||
gap: 0;
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
gap: 5px;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
white-space: nowrap;
|
||||
color: var(--color-text-tab);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
background-color: var(--color-background-tab);
|
||||
|
||||
&:not(.active) {
|
||||
&:hover {
|
||||
background-color: var(--color-background-tab-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.active,
|
||||
:target {
|
||||
background-color: var(--color-background);
|
||||
|
||||
a {
|
||||
color: var(--color-text-tab-active);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0.7em;
|
||||
color: var(--color-text-tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
changedetectionio/static/styles/scss/parts/_toast.scss
Normal file
231
changedetectionio/static/styles/scss/parts/_toast.scss
Normal file
@@ -0,0 +1,231 @@
|
||||
// Toast Notification System
|
||||
// Modern, animated toast notifications
|
||||
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
pointer-events: none;
|
||||
z-index: 10000;
|
||||
|
||||
// Positioning
|
||||
&.toast-top-right {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
&.toast-top-center {
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.toast-top-left {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
&.toast-bottom-right {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
&.toast-bottom-center {
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.toast-bottom-left {
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
padding: 1rem 1.25rem;
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
font-family: inherit;
|
||||
|
||||
&.toast-show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.toast-hide {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) scale(0.95);
|
||||
}
|
||||
|
||||
// Toast types
|
||||
&.toast-success {
|
||||
border-left: 4px solid #10b981;
|
||||
|
||||
.toast-icon {
|
||||
color: #10b981;
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-error {
|
||||
border-left: 4px solid #ef4444;
|
||||
|
||||
.toast-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-warning {
|
||||
border-left: 4px solid #f59e0b;
|
||||
|
||||
.toast-icon {
|
||||
color: #f59e0b;
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-info {
|
||||
border-left: 4px solid #3b82f6;
|
||||
|
||||
.toast-icon {
|
||||
color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-default {
|
||||
border-left: 4px solid var(--color-grey-500);
|
||||
}
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-message {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text);
|
||||
word-break: break-word;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--color-grey-500);
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
margin-left: 0.25rem;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-grey-800);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.toast-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: currentColor;
|
||||
opacity: 0.3;
|
||||
transform-origin: left;
|
||||
transition: transform linear;
|
||||
}
|
||||
|
||||
// Dark mode adjustments
|
||||
html[data-darkmode=true] {
|
||||
.toast {
|
||||
background: var(--color-grey-300);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.toast-close:hover {
|
||||
background: var(--color-grey-400);
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile adjustments
|
||||
@media only screen and (max-width: 768px) {
|
||||
.toast-container {
|
||||
left: 10px !important;
|
||||
right: 10px !important;
|
||||
top: 10px !important;
|
||||
transform: none !important;
|
||||
align-items: stretch;
|
||||
|
||||
&.toast-bottom-right,
|
||||
&.toast-bottom-center,
|
||||
&.toast-bottom-left {
|
||||
top: auto !important;
|
||||
bottom: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.toast {
|
||||
min-width: auto;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
transform: translateY(-100px);
|
||||
|
||||
&.toast-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.toast-hide {
|
||||
transform: translateY(-100px) scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accessibility
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.toast {
|
||||
transition: opacity 0.2s ease;
|
||||
transform: none !important;
|
||||
|
||||
&.toast-show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.toast-hide {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
* -- BASE STYLES --
|
||||
*/
|
||||
|
||||
@use "settings" as *;
|
||||
@use "parts/variables";
|
||||
@use "parts/arrows";
|
||||
@use "parts/browser-steps";
|
||||
@@ -23,6 +24,14 @@
|
||||
@use "parts/widgets";
|
||||
@use "parts/diff_image";
|
||||
@use "parts/modal";
|
||||
@use "parts/language";
|
||||
@use "parts/action_sidebar";
|
||||
@use "parts/hamburger_menu";
|
||||
@use "parts/search_modal";
|
||||
@use "parts/notification_bubble";
|
||||
@use "parts/toast";
|
||||
@use "parts/login_form";
|
||||
@use "parts/tabs";
|
||||
|
||||
|
||||
body {
|
||||
@@ -71,20 +80,6 @@ a.github-link {
|
||||
}
|
||||
}
|
||||
|
||||
#search-q {
|
||||
opacity: 0;
|
||||
-webkit-transition: all .9s ease;
|
||||
-moz-transition: all .9s ease;
|
||||
transition: all .9s ease;
|
||||
width: 0;
|
||||
display: none;
|
||||
&.expanded {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
#search-result-info {
|
||||
color: #fff;
|
||||
}
|
||||
@@ -165,7 +160,13 @@ body.spinner-active {
|
||||
}
|
||||
|
||||
section.content {
|
||||
padding-top: 100px;
|
||||
@media only screen and (max-width: $desktop-wide-breakpoint) {
|
||||
padding-top: 80px;
|
||||
}
|
||||
@media only screen and (min-width: $desktop-wide-breakpoint) {
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
padding-bottom: 1em;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
@@ -183,13 +184,13 @@ code {
|
||||
border-radius: 5px;
|
||||
padding: 2px 5px;
|
||||
margin-right: 4px;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
|
||||
/* Processor type badges - colors auto-generated from processor names */
|
||||
.processor-badge {
|
||||
@extend .inline-tag;
|
||||
font-size: 0.85em;
|
||||
font-weight: 500;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.watch-tag-list {
|
||||
@@ -522,6 +523,9 @@ footer {
|
||||
}
|
||||
|
||||
.sticky-tab {
|
||||
@media only screen and (max-width: $desktop-wide-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
font-size: 65%;
|
||||
@@ -666,7 +670,7 @@ footer {
|
||||
|
||||
|
||||
@media only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
(min-device-width: 768px) and (max-device-width: $desktop-wide-breakpoint) {
|
||||
.edit-form {
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
@@ -678,30 +682,10 @@ footer {
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 800px) {
|
||||
|
||||
div.sticky-tab#hosted-sticky {
|
||||
top: 60px;
|
||||
left: 0px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
section.content {
|
||||
padding-top: 110px;
|
||||
}
|
||||
|
||||
// Make the tabs easier to hit, they will be all nice and horizontal
|
||||
div.tabs.collapsable ul li {
|
||||
display: block;
|
||||
border-radius: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: $desktop-wide-breakpoint) {
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.pure-table {
|
||||
@@ -777,45 +761,6 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
|
||||
.tabs {
|
||||
ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
display: block;
|
||||
|
||||
li {
|
||||
margin-right: 1px;
|
||||
display: inline-block;
|
||||
color: var(--color-text-tab);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
background-color: var(--color-background-tab);
|
||||
|
||||
&:not(.active) {
|
||||
&:hover {
|
||||
background-color: var(--color-background-tab-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.active,
|
||||
:target {
|
||||
background-color: var(--color-background);
|
||||
|
||||
a {
|
||||
color: var(--color-text-tab-active);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0.7em;
|
||||
color: var(--color-text-tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form-edge-padding: 20px;
|
||||
|
||||
.pure-form-stacked {
|
||||
@@ -824,14 +769,7 @@ $form-edge-padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
.inner {
|
||||
background: var(--color-background);
|
||||
;
|
||||
padding: $form-edge-padding;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
// Login form styles moved to parts/_login_form.scss
|
||||
|
||||
.tab-pane-inner {
|
||||
|
||||
@@ -1159,44 +1097,4 @@ ul#highlightSnippetActions {
|
||||
}
|
||||
}
|
||||
|
||||
// Language Selector Modal Styles
|
||||
.language-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.language-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-menu-link-hover);
|
||||
border-color: var(--color-border-table-cell);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-link);
|
||||
color: var(--color-text-button);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.language-name {
|
||||
flex-grow: 1;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -145,6 +145,7 @@
|
||||
<div id="notification-test-log" style="display: none;"><span class="pure-form-message-inline">Processing..</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group grey-form-border">
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.notification_title, class="m-d notification-title", placeholder=settings_application['notification_title']) }}
|
||||
@@ -169,6 +170,7 @@
|
||||
</span></li>
|
||||
</ul>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
{{ render_field(form.notification_format , class="notification-format") }}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{% for idx, entry_errors in field.errors|enumerate %}
|
||||
{% if entry_errors is mapping and entry_errors %}
|
||||
{# Only show entries that have actual errors #}
|
||||
<li><strong>Entry {{ idx + 1 }}:</strong>
|
||||
<li><strong>{{ _('Entry') }} {{ idx + 1 }}:</strong>
|
||||
<ul>
|
||||
{% for field_name, messages in entry_errors.items() %}
|
||||
{% for message in messages %}
|
||||
@@ -150,7 +150,7 @@
|
||||
{% for subfield in fieldlist[0] %}
|
||||
<div class="fieldlist-header-cell">{{ subfield.label }}</div>
|
||||
{% endfor %}
|
||||
<div class="fieldlist-header-cell">Actions</div>
|
||||
<div class="fieldlist-header-cell">{{ _('Actions') }}</div>
|
||||
</div>
|
||||
<div class="fieldlist-body">
|
||||
{% for form_row in fieldlist %}
|
||||
@@ -169,9 +169,9 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="fieldlist-cell fieldlist-actions">
|
||||
<button type="button" class="addRuleRow" title="Add a row/rule after">+</button>
|
||||
<button type="button" class="removeRuleRow" title="Remove this row/rule">-</button>
|
||||
<button type="button" class="verifyRuleRow" title="Verify this rule against current snapshot">✓</button>
|
||||
<button type="button" class="addRuleRow" title="{{ _('Add a row/rule after') }}">+</button>
|
||||
<button type="button" class="removeRuleRow" title="{{ _('Remove this row/rule') }}">-</button>
|
||||
<button type="button" class="verifyRuleRow" title="{{ _('Verify this rule against current snapshot') }}">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -181,8 +181,8 @@
|
||||
|
||||
|
||||
{% macro playwright_warning() %}
|
||||
<p><strong>Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled.</strong> Alternatively try our <a href="https://changedetection.io">very affordable subscription based service which has all this setup for you</a>.</p>
|
||||
<p>You may need to <a href="https://github.com/dgtlmoon/changedetection.io/blob/09ebc6ec6338545bdd694dc6eee57f2e9d2b8075/docker-compose.yml#L31">Enable playwright environment variable</a> and uncomment the <strong>sockpuppetbrowser</strong> in the <a href="https://github.com/dgtlmoon/changedetection.io/blob/master/docker-compose.yml">docker-compose.yml</a> file.</p>
|
||||
<p><strong>{{ _('Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled.') }}</strong> {{ _('Alternatively try our') }} <a href="https://changedetection.io">{{ _('very affordable subscription based service which has all this setup for you') }}</a>.</p>
|
||||
<p>{{ _('You may need to') }} <a href="https://github.com/dgtlmoon/changedetection.io/blob/09ebc6ec6338545bdd694dc6eee57f2e9d2b8075/docker-compose.yml#L31">{{ _('Enable playwright environment variable') }}</a> {{ _('and uncomment the') }} <strong>sockpuppetbrowser</strong> {{ _('in the') }} <a href="https://github.com/dgtlmoon/changedetection.io/blob/master/docker-compose.yml">docker-compose.yml</a> {{ _('file') }}.</p>
|
||||
<br>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -237,18 +237,17 @@
|
||||
<span id="scheduler-icon-label" style="">
|
||||
{{ render_checkbox_field(form.time_schedule_limit.enabled) }}
|
||||
<div class="pure-form-message-inline">
|
||||
Set a hourly/week day schedule
|
||||
{{ _('Set a hourly/week day schedule') }}
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<div id="schedule-day-limits-wrapper">
|
||||
<label>Schedule time limits</label><a data-template="business-hours"
|
||||
class="set-schedule pure-button button-secondary button-xsmall">Business
|
||||
hours</a>
|
||||
<a data-template="weekend" class="set-schedule pure-button button-secondary button-xsmall">Weekends</a>
|
||||
<a data-template="reset" class="set-schedule pure-button button-xsmall">Reset</a><br>
|
||||
<label>{{ _('Schedule time limits') }}</label><a data-template="business-hours"
|
||||
class="set-schedule pure-button button-secondary button-xsmall">{{ _('Business hours') }}</a>
|
||||
<a data-template="weekend" class="set-schedule pure-button button-secondary button-xsmall">{{ _('Weekends') }}</a>
|
||||
<a data-template="reset" class="set-schedule pure-button button-xsmall">{{ _('Reset') }}</a><br>
|
||||
<br>
|
||||
|
||||
<ul id="day-wrapper">
|
||||
@@ -257,8 +256,8 @@
|
||||
{{ render_nolabel_field(form.time_schedule_limit[day]) }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li id="timespan-warning">Warning, one or more of your 'days' has a duration that would extend into the next day.<br>
|
||||
This could have unintended consequences.</li>
|
||||
<li id="timespan-warning">{{ _("Warning, one or more of your 'days' has a duration that would extend into the next day.") }}<br>
|
||||
{{ _('This could have unintended consequences.') }}</li>
|
||||
<li id="timezone-info">
|
||||
{{ render_field(form.time_schedule_limit.timezone, placeholder=timezone_default_config) }} <span id="local-time-in-tz"></span>
|
||||
<datalist id="timezones" style="display: none;">
|
||||
@@ -268,12 +267,12 @@
|
||||
</ul>
|
||||
<br>
|
||||
<span class="pure-form-message-inline">
|
||||
<a href="https://changedetection.io/tutorials">More help and examples about using the scheduler</a>
|
||||
<a href="https://changedetection.io/tutorials">{{ _('More help and examples about using the scheduler') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="pure-form-message-inline">
|
||||
Want to use a time schedule? <a href="{{url_for('settings.settings_page')}}#timedate">First confirm/save your Time Zone Settings</a>
|
||||
{{ _('Want to use a time schedule?') }} <a href="{{url_for('settings.settings_page')}}#timedate">{{ _('First confirm/save your Time Zone Settings') }}</a>
|
||||
</span>
|
||||
<br>
|
||||
{% endif %}
|
||||
@@ -282,8 +281,8 @@
|
||||
|
||||
{% macro highlight_trigger_ignored_explainer() %}
|
||||
<p>
|
||||
<span title="Triggers a change if this text appears, AND something changed in the document." style="background-color: var(--highlight-trigger-text-bg-color); color: #fff; padding: 4px; border-radius: 2px; margin-right: 4px;">Triggered text</span>
|
||||
<span title="Ignored for calculating changes, but still shown." style="background-color: var(--highlight-ignored-text-bg-color); color: #fff; padding: 4px; border-radius: 2px; margin-right: 4px;">Ignored text</span>
|
||||
<span title="No change-detection will occur because this text exists." style="background-color: var(--highlight-blocked-text-bg-color); color: #fff; padding: 4px; border-radius: 2px; margin-right: 4px;">Blocked text</span>
|
||||
<span title="{{ _('Triggers a change if this text appears, AND something changed in the document.') }}" style="background-color: var(--highlight-trigger-text-bg-color); color: #fff; padding: 4px; border-radius: 2px; margin-right: 4px;">{{ _('Triggered text') }}</span>
|
||||
<span title="{{ _('Ignored for calculating changes, but still shown.') }}" style="background-color: var(--highlight-ignored-text-bg-color); color: #fff; padding: 4px; border-radius: 2px; margin-right: 4px;">{{ _('Ignored text') }}</span>
|
||||
<span title="{{ _('No change-detection will occur because this text exists.') }}" style="background-color: var(--highlight-blocked-text-bg-color); color: #fff; padding: 4px; border-radius: 2px; margin-right: 4px;">{{ _('Blocked text') }}</span>
|
||||
</p>
|
||||
{% endmacro %}
|
||||
@@ -53,10 +53,10 @@
|
||||
<div class="home-menu pure-menu pure-menu-horizontal" id="nav-menu">
|
||||
|
||||
{% if has_password and not current_user.is_authenticated %}
|
||||
<a class="pure-menu-heading" href="https://changedetection.io" rel="noopener">
|
||||
<a id="cdio-logo" class="pure-menu-heading" href="https://changedetection.io" rel="noopener">
|
||||
<strong>Change</strong>Detection.io</a>
|
||||
{% else %}
|
||||
<a class="pure-menu-heading" href="{{url_for('watchlist.index')}}">
|
||||
<a id="cdio-logo" class="pure-menu-heading" href="{{url_for('watchlist.index')}}">
|
||||
<strong>Change</strong>Detection.io</a>
|
||||
{% endif %}
|
||||
{% if current_diff_url and is_safe_valid_url(current_diff_url) %}
|
||||
@@ -71,64 +71,20 @@
|
||||
{% endif %}
|
||||
|
||||
<ul class="pure-menu-list" id="top-right-menu">
|
||||
<!-- Collapsible menu items (hidden on mobile, shown in drawer) -->
|
||||
{% include "menu.html" %}
|
||||
|
||||
{% if current_user.is_authenticated or not has_password %}
|
||||
{% if not current_diff_url %}
|
||||
<li class="pure-menu-item">
|
||||
<a href="{{ url_for('tags.tags_overview_page')}}" class="pure-menu-link">{{ _('GROUPS') }}</a>
|
||||
</li>
|
||||
<li class="pure-menu-item">
|
||||
<a href="{{ url_for('settings.settings_page')}}" class="pure-menu-link">{{ _('SETTINGS') }}</a>
|
||||
</li>
|
||||
<li class="pure-menu-item">
|
||||
<a href="{{ url_for('imports.import_page')}}" class="pure-menu-link">{{ _('IMPORT') }}</a>
|
||||
</li>
|
||||
<li class="pure-menu-item">
|
||||
<a href="{{ url_for('backups.index')}}" class="pure-menu-link">{{ _('BACKUPS') }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="pure-menu-item">
|
||||
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=uuid, next='diff') }}" class="pure-menu-link">{{ _('EDIT') }}</a>
|
||||
<li class="pure-menu-item menu-collapsible">
|
||||
<button class="toggle-button" id="open-search-modal" type="button" title="{{ _('Search, or Use Alt+S Key') }}">
|
||||
{% include "svgs/search-icon.svg" %}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<li class="pure-menu-item">
|
||||
<a class="pure-menu-link" href="https://changedetection.io">Website Change Detection and Notification.</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="pure-menu-item">
|
||||
<a href="{{url_for('logout', redirect=request.path)}}" class="pure-menu-link">{{ _('LOG OUT') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated or not has_password %}
|
||||
<li class="pure-menu-item pure-form" id="search-menu-item">
|
||||
<!-- We use GET here so it offers people a chance to set bookmarks etc -->
|
||||
<form name="searchForm" action="" method="GET">
|
||||
<input id="search-q" class="" name="q" placeholder="URL or Title {% if active_tag_uuid %}in '{{ active_tag.title }}'{% endif %}" required="" type="text" value="">
|
||||
<input name="tags" type="hidden" value="{% if active_tag_uuid %}{{active_tag_uuid}}{% endif %}">
|
||||
<button class="toggle-button " id="toggle-search" type="button" title="{{ _('Search, or Use Alt+S Key') }}" >
|
||||
{% include "svgs/search-icon.svg" %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="pure-menu-item">
|
||||
<button class="toggle-button" id ="toggle-light-mode" type="button" title="{{ _('Toggle Light/Dark Mode') }}">
|
||||
<span class="visually-hidden">{{ _('Toggle light/dark mode') }}</span>
|
||||
<span class="icon-light">
|
||||
{% include "svgs/light-mode-toggle-icon.svg" %}
|
||||
</span>
|
||||
<span class="icon-dark">
|
||||
{% include "svgs/dark-mode-toggle-icon.svg" %}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="pure-menu-item">
|
||||
<button class="toggle-button" id="language-selector" type="button" title="{{ _('Change Language') }}">
|
||||
<span class="visually-hidden">{{ _('Change language') }}</span>
|
||||
<span class="{{ get_flag_for_locale(get_locale()) }}" style="display: inline-block; width: 1.2em; height: 1.2em; vertical-align: middle; border-radius: 50%; overflow: hidden;"></span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="pure-menu-item" id="heart-us">
|
||||
<svg
|
||||
fill="#ff0000"
|
||||
@@ -138,24 +94,37 @@
|
||||
id="svg-heart"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path id="heartpath" d="M 5.338316,0.50302766 C 0.71136983,0.50647126 -3.9576371,7.2707777 8.5004254,15.503028 23.833425,5.3700277 13.220206,-2.5384409 8.6762066,1.6475589 c -0.060791,0.054322 -0.11943,0.1110064 -0.1757812,0.1699219 -0.057,-0.059 -0.1157813,-0.116875 -0.1757812,-0.171875 C 7.4724566,0.86129334 6.4060729,0.50223298 5.338316,0.50302766 Z"
|
||||
style="fill:var(--color-background);fill-opacity:1;stroke:#ff0000;stroke-opacity:1" />
|
||||
<path id="heartpath" d="M 5.338316,0.50302766 C 0.71136983,0.50647126 -3.9576371,7.2707777 8.5004254,15.503028 23.833425,5.3700277 13.220206,-2.5384409 8.6762066,1.6475589 c -0.060791,0.054322 -0.11943,0.1110064 -0.1757812,0.1699219 -0.057,-0.059 -0.1157813,-0.116875 -0.1757812,-0.171875 C 7.4724566,0.86129334 6.4060729,0.50223298 5.338316,0.50302766 Z" style="fill:var(--color-background);fill-opacity:1;stroke:#ff0000;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
||||
</li>
|
||||
<!-- Hamburger menu button (mobile only) -->
|
||||
<li class="pure-menu-item">
|
||||
<a class="github-link" href="https://github.com/dgtlmoon/changedetection.io">
|
||||
{% include "svgs/github.svg" %}
|
||||
</a>
|
||||
<button class="hamburger-menu" id="hamburger-toggle" aria-label="Toggle menu">
|
||||
<div class="hamburger-icon">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu drawer -->
|
||||
<div class="mobile-menu-overlay" id="mobile-menu-overlay"></div>
|
||||
<div class="mobile-menu-drawer" id="mobile-menu-drawer">
|
||||
<ul class="mobile-menu-items">
|
||||
{% include "menu.html" %}
|
||||
<li class="pure-menu-item menu-collapsible">
|
||||
{%- if right_sticky -%}<div>{{ right_sticky }}</div>{%- endif -%}
|
||||
<a href="https://changedetection.io/?ref={{ guid }}">Let us host your instance!</a><br>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="pure-menu-horizontal-spinner"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% if hosted_sticky %}
|
||||
<div class="sticky-tab" id="hosted-sticky">
|
||||
<a href="https://changedetection.io/?ref={{guid}}">Let us host your instance!</a>
|
||||
@@ -218,32 +187,62 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories = true) %}
|
||||
{% if
|
||||
messages %}
|
||||
<ul class="messages">
|
||||
{% for category, message in messages %}
|
||||
<li class="{{ category }}">{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% if session['share-link'] %}
|
||||
<ul class="messages with-share-link">
|
||||
<li class="message">
|
||||
Share this link:
|
||||
<span id="share-link">{{ session['share-link'] }}</span>
|
||||
<img style="height: 1em; display: inline-block" src="{{url_for('static_content', group='images', filename='copy.svg')}}" >
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
<div class="content-wrapper">
|
||||
{#
|
||||
{% if current_user.is_authenticated or not has_password %}
|
||||
<aside class="action-sidebar">
|
||||
<a href="{{ url_for('watchlist.index') }}" class="action-sidebar-item {% if request.endpoint.startswith('watchlist.') or request.endpoint.startswith('ui.') %}active{% endif %}" title="{{ _('Watch List') }}">
|
||||
<svg class="action-icon" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v6l4 2"/>
|
||||
</svg>
|
||||
<span class="action-label">{{ _('Watches') }}</span>
|
||||
</a>
|
||||
<a href="{{ url_for('queue_status') }}" class="action-sidebar-item {% if request.endpoint == 'queue_status' %}active{% endif %}" id="queue-action-item" title="{{ _('Queue Status') }}">
|
||||
<svg class="action-icon" viewBox="0 0 24 24">
|
||||
<line x1="8" y1="6" x2="21" y2="6"/>
|
||||
<line x1="8" y1="12" x2="21" y2="12"/>
|
||||
<line x1="8" y1="18" x2="21" y2="18"/>
|
||||
<line x1="3" y1="6" x2="3.01" y2="6"/>
|
||||
<line x1="3" y1="12" x2="3.01" y2="12"/>
|
||||
<line x1="3" y1="18" x2="3.01" y2="18"/>
|
||||
</svg>
|
||||
<span class="action-label">{{ _('Queue') }}</span>
|
||||
<span class="notification-bubble blue-bubble" id="queue-bubble" data-count="0"></span>
|
||||
</a>
|
||||
</aside>
|
||||
{% endif %}
|
||||
#}
|
||||
<div class="content-main">
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories = true) %}
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for category, message in messages %}
|
||||
<li class="{{ category }}">{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% if session['share-link'] %}
|
||||
<ul class="messages with-share-link">
|
||||
<li class="message">
|
||||
Share this link:
|
||||
<span id="share-link">{{ session['share-link'] }}</span>
|
||||
<img style="height: 1em; display: inline-block" src="{{url_for('static_content', group='images', filename='copy.svg')}}" >
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script src="{{url_for('static_content', group='js', filename='toggle-theme.js')}}" defer></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='hamburger-menu.js')}}" defer></script>
|
||||
|
||||
<div id="checking-now-fixed-tab" style="display: none;"><span class="spinner"></span><span class="status-text"> {{ _('Checking now') }}</span></div>
|
||||
<div id="realtime-conn-error" style="display:none">{{ _('Real-time updates offline') }}</div>
|
||||
@@ -262,7 +261,7 @@
|
||||
<div class="language-list">
|
||||
{% for locale, lang_data in available_languages.items()|sort %}
|
||||
<a href="{{ url_for('set_language', locale=locale, redirect=request.path) }}" class="language-option" data-locale="{{ locale }}">
|
||||
<span class="{{ lang_data.flag }}" style="display: inline-block; width: 1.5em; height: 1.5em; vertical-align: middle; margin-right: 0.5em; border-radius: 50%; overflow: hidden;"></span> <span class="language-name">{{ lang_data.name }}</span>
|
||||
<span class="lang-option {{ lang_data.flag }}"></span> <span class="language-name">{{ lang_data.name }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -275,7 +274,103 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- Search Modal -->
|
||||
{% if current_user.is_authenticated or not has_password %}
|
||||
<dialog id="search-modal" class="modal-dialog" aria-labelledby="search-modal-title">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="search-modal-title">{{ _('Search') }}</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="search-form" method="GET">
|
||||
<div class="pure-control-group">
|
||||
<label for="search-modal-input">{{ _('URL or Title') }}{% if active_tag_uuid %} {{ _('in') }} '{{ active_tag.title }}'{% endif %}</label>
|
||||
<input id="search-modal-input" class="m-d" name="q" placeholder="{{ _('Enter search term...') }}" required type="text" value="" autofocus>
|
||||
<input name="tags" type="hidden" value="{% if active_tag_uuid %}{{active_tag_uuid}}{% endif %}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="pure-button button-cancel" id="close-search-modal">{{ _('Cancel') }}</button>
|
||||
<button type="submit" form="search-form" class="pure-button pure-button-primary">{{ _('Search') }}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
/* AUTOMATIC TAB COLUMN-IZER FOR WHEN TABS WRAP */
|
||||
// Exit early if no tabs on page
|
||||
if (!document.querySelector('.tab')) return;
|
||||
|
||||
const cache = new Map();
|
||||
|
||||
function checkWrapping(ul) {
|
||||
const tabs = ul.querySelectorAll('.tab');
|
||||
if (tabs.length < 2) return false;
|
||||
|
||||
// Init cache on first run
|
||||
if (!cache.has(ul)) {
|
||||
ul.style.setProperty('--tab-width', '');
|
||||
void ul.offsetHeight;
|
||||
let max = 0;
|
||||
tabs.forEach(t => max = Math.max(max, t.offsetWidth));
|
||||
cache.set(ul, max);
|
||||
}
|
||||
|
||||
// Temporarily use flex wrap to check if wrapping occurs
|
||||
ul.style.display = 'flex';
|
||||
ul.style.flexWrap = 'wrap';
|
||||
void ul.offsetHeight;
|
||||
|
||||
const top = tabs[0].offsetTop;
|
||||
const wrapped = Array.from(tabs).some((t, i) => i > 0 && t.offsetTop !== top);
|
||||
|
||||
// Reset display to use CSS grid
|
||||
ul.style.display = '';
|
||||
ul.style.flexWrap = '';
|
||||
|
||||
// Set CSS variable for wrapped mode
|
||||
if (wrapped) {
|
||||
ul.style.setProperty('--tab-width', `${cache.get(ul) + 10}px`);
|
||||
} else {
|
||||
ul.style.setProperty('--tab-width', '');
|
||||
}
|
||||
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
function check() {
|
||||
let any = false;
|
||||
document.querySelectorAll('ul').forEach(ul => {
|
||||
if (ul.querySelector('.tab') && checkWrapping(ul)) any = true;
|
||||
});
|
||||
document.body.classList.toggle('wrapped-tabs', any);
|
||||
}
|
||||
|
||||
check();
|
||||
let timer;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(check, 100);
|
||||
});
|
||||
|
||||
// Re-check wrapping when tabs are switched via anchors
|
||||
window.addEventListener('hashchange', () => {
|
||||
clearTimeout(timer);
|
||||
// Use requestAnimationFrame + setTimeout to ensure DOM has settled
|
||||
requestAnimationFrame(() => {
|
||||
timer = setTimeout(check, 0);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<script src="{{url_for('static_content', group='js', filename='language-selector.js')}}" defer></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='search-modal.js')}}" defer></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='toast.js')}}"></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='flask-toast-bridge.js')}}" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
54
changedetectionio/templates/menu.html
Normal file
54
changedetectionio/templates/menu.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{# Menu items template - used for both desktop and mobile menus #}
|
||||
{# CSS media queries handle which version displays - no need for conditional classes #}
|
||||
|
||||
|
||||
{% if current_user.is_authenticated or not has_password %}
|
||||
{% if not current_diff_url %}
|
||||
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('tags.') %}active{% endif %}">
|
||||
<a href="{{ url_for('tags.tags_overview_page') }}" class="pure-menu-link">{{ _('GROUPS') }}</a>
|
||||
</li>
|
||||
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('settings.') %}active{% endif %}">
|
||||
<a href="{{ url_for('settings.settings_page') }}" class="pure-menu-link">{{ _('SETTINGS') }}</a>
|
||||
</li>
|
||||
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('imports.') %}active{% endif %}">
|
||||
<a href="{{ url_for('imports.import_page') }}" class="pure-menu-link">{{ _('IMPORT') }}</a>
|
||||
</li>
|
||||
<li class="pure-menu-item menu-collapsible {% if request.endpoint.startswith('backups.') %}active{% endif %}">
|
||||
<a href="{{ url_for('backups.index') }}" class="pure-menu-link">{{ _('BACKUPS') }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="pure-menu-item menu-collapsible">
|
||||
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=uuid, next='diff') }}"
|
||||
class="pure-menu-link">{{ _('EDIT') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{%- if current_user.is_authenticated -%}
|
||||
<li class="pure-menu-item menu-collapsible">
|
||||
<a href="{{ url_for('logout', redirect=request.path) }}" class="pure-menu-link">{{ _('LOG OUT') }}</a>
|
||||
</li>
|
||||
{%- endif -%}
|
||||
|
||||
{% else %}
|
||||
<li class="pure-menu-item menu-collapsible">
|
||||
<a class="pure-menu-link" href="https://changedetection.io">Website Change Detection and Notification.</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="pure-menu-item menu-collapsible" id="inline-menu-extras-group">
|
||||
<button class="toggle-button toggle-light-mode " type="button" title="{{ _('Toggle Light/Dark Mode') }}">
|
||||
<span class="visually-hidden">{{ _('Toggle light/dark mode') }}</span>
|
||||
<span class="icon-light">
|
||||
{% include "svgs/light-mode-toggle-icon.svg" %}
|
||||
</span>
|
||||
<span class="icon-dark">
|
||||
{% include "svgs/dark-mode-toggle-icon.svg" %}
|
||||
</span>
|
||||
</button>
|
||||
<button class="toggle-button language-selector" type="button" title="{{ _('Change Language') }}">
|
||||
<span class="visually-hidden">{{ _('Change language') }}</span>
|
||||
<span class="{{ get_flag_for_locale(get_locale()) }}" id="language-selector-flag"></span>
|
||||
</button>
|
||||
<a class="github-link" href="https://github.com/dgtlmoon/changedetection.io">
|
||||
{% include "svgs/github.svg" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
from flask import url_for
|
||||
from loguru import logger
|
||||
|
||||
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks
|
||||
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks, wait_for_notification_endpoint_output
|
||||
from . util import extract_UUID_from_client
|
||||
import logging
|
||||
import base64
|
||||
@@ -83,7 +83,9 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
|
||||
|
||||
|
||||
uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
|
||||
with open(os.path.join(datastore_path, str(uuid), 'last-screenshot.png'), 'wb') as f:
|
||||
screenshot_dir = os.path.join(datastore_path, str(uuid))
|
||||
os.makedirs(screenshot_dir, exist_ok=True)
|
||||
with open(os.path.join(screenshot_dir, 'last-screenshot.png'), 'wb') as f:
|
||||
f.write(base64.b64decode(testimage_png))
|
||||
|
||||
# Goto the edit page, add our ignore text
|
||||
@@ -142,7 +144,7 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
|
||||
# Trigger a check
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
time.sleep(6)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
# Check no errors were recorded
|
||||
res = client.get(url_for("watchlist.index"))
|
||||
@@ -199,7 +201,7 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
|
||||
set_more_modified_response(datastore_path=datastore_path)
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
time.sleep(6)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
# Verify what was sent as a notification, this file should exist
|
||||
with open(os.path.join(datastore_path, "notification.txt"), "r") as f:
|
||||
notification_submission = f.read()
|
||||
@@ -240,7 +242,8 @@ def test_check_notification(client, live_server, measure_memory_usage, datastore
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
|
||||
time.sleep(2)
|
||||
wait_for_all_checks(client)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
# Verify what was sent as a notification, this file should exist
|
||||
with open(os.path.join(datastore_path, "notification.txt"), "r") as f:
|
||||
@@ -325,7 +328,7 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server, measure_me
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
time.sleep(2) # plus extra delay for notifications to fire
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
|
||||
# Check no errors were recorded, because we asked for 204 which is slightly uncommon but is still OK
|
||||
@@ -443,7 +446,7 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage
|
||||
assert res.status_code != 500
|
||||
|
||||
# Give apprise time to fire
|
||||
time.sleep(4)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||
x = f.read()
|
||||
@@ -500,7 +503,7 @@ def test_single_send_test_notification_on_watch(client, live_server, measure_mem
|
||||
|
||||
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}&+custom-header=123"
|
||||
# 1995 UTF-8 content should be encoded
|
||||
test_body = 'change detection is cool 网站监测 内容更新了 - {{diff_full}}'
|
||||
test_body = 'change detection is cool 网站监测 内容更新了 - {{diff_full}}\n\nCurrent snapshot: {{current_snapshot}}'
|
||||
######### Test global/system settings
|
||||
res = client.post(
|
||||
url_for("ui.ui_notification.ajax_callback_send_notification_test")+f"/{uuid}",
|
||||
@@ -525,7 +528,8 @@ def test_single_send_test_notification_on_watch(client, live_server, measure_mem
|
||||
assert 'title="Changed into">Example text:' not in x
|
||||
assert 'span' not in x
|
||||
assert 'Example text:' in x
|
||||
|
||||
#3720 current_snapshot check, was working but lets test it exactly.
|
||||
assert 'Current snapshot: Example text: example test' in x
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
@@ -572,7 +576,7 @@ def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
assert b'Queued 1 watch for rechecking.' in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
time.sleep(2)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||
x = f.read()
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
changedetectionio/translations/en_GB/LC_MESSAGES/messages.mo
Normal file
BIN
changedetectionio/translations/en_GB/LC_MESSAGES/messages.mo
Normal file
Binary file not shown.
2268
changedetectionio/translations/en_GB/LC_MESSAGES/messages.po
Normal file
2268
changedetectionio/translations/en_GB/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
changedetectionio/translations/en_US/LC_MESSAGES/messages.mo
Normal file
BIN
changedetectionio/translations/en_US/LC_MESSAGES/messages.mo
Normal file
Binary file not shown.
2268
changedetectionio/translations/en_US/LC_MESSAGES/messages.po
Normal file
2268
changedetectionio/translations/en_US/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-01-02 16:07+0100\n"
|
||||
"POT-Creation-Date: 2026-01-03 14:31+0100\n"
|
||||
"PO-Revision-Date: 2026-01-02 15:32+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: it\n"
|
||||
@@ -19,21 +19,21 @@ msgstr ""
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:241
|
||||
#: changedetectionio/flask_app.py:213 changedetectionio/flask_app.py:225
|
||||
#: changedetectionio/flask_app.py:246
|
||||
#: changedetectionio/flask_app.py:214 changedetectionio/flask_app.py:226
|
||||
#: changedetectionio/flask_app.py:247
|
||||
#: changedetectionio/realtime/socket_server.py:171
|
||||
msgid "Not yet"
|
||||
msgstr "Non ancora"
|
||||
|
||||
#: changedetectionio/flask_app.py:468
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr "Devi essere autenticato, effettua l'accesso."
|
||||
|
||||
#: changedetectionio/flask_app.py:495
|
||||
#: changedetectionio/flask_app.py:534
|
||||
msgid "Already logged in"
|
||||
msgstr "Già autenticato"
|
||||
|
||||
#: changedetectionio/flask_app.py:522
|
||||
#: changedetectionio/flask_app.py:536
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr "Devi essere autenticato, effettua l'accesso."
|
||||
|
||||
#: changedetectionio/flask_app.py:551
|
||||
msgid "Incorrect password"
|
||||
msgstr "Password errata"
|
||||
|
||||
@@ -175,16 +175,15 @@ msgstr "Valore non valido."
|
||||
|
||||
#: changedetectionio/forms.py:732
|
||||
msgid "Watch"
|
||||
msgstr "Osserva"
|
||||
msgstr "Monitora"
|
||||
|
||||
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
|
||||
msgid "Processor"
|
||||
msgstr "Processore"
|
||||
|
||||
#: changedetectionio/forms.py:734
|
||||
#, fuzzy
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Modifica prima poi Monitora"
|
||||
msgstr "Modifica > Monitora"
|
||||
|
||||
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
|
||||
msgid "Fetch Method"
|
||||
@@ -415,9 +414,8 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
|
||||
#, fuzzy
|
||||
msgid "Name"
|
||||
msgstr "Riattiva audio"
|
||||
msgstr "Nome"
|
||||
|
||||
#: changedetectionio/forms.py:921
|
||||
msgid "Proxy URL"
|
||||
@@ -440,9 +438,8 @@ msgid "Plaintext requests"
|
||||
msgstr "Richieste in chiaro"
|
||||
|
||||
#: changedetectionio/forms.py:946
|
||||
#, fuzzy
|
||||
msgid "Chrome requests"
|
||||
msgstr "Richiesta"
|
||||
msgstr "Richieste Chrome"
|
||||
|
||||
#: changedetectionio/forms.py:952
|
||||
msgid "Default proxy"
|
||||
@@ -497,9 +494,8 @@ msgid "API access token security check enabled"
|
||||
msgstr "Controllo sicurezza token API attivo"
|
||||
|
||||
#: changedetectionio/forms.py:989
|
||||
#, fuzzy
|
||||
msgid "Notification base URL override"
|
||||
msgstr "Notifiche"
|
||||
msgstr "URL base notifiche"
|
||||
|
||||
#: changedetectionio/forms.py:993
|
||||
msgid "Treat empty pages as a change?"
|
||||
@@ -605,6 +601,8 @@ msgid "Backups were deleted."
|
||||
msgstr "I backup sono stati eliminati."
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:6
|
||||
#: changedetectionio/templates/base.html:282
|
||||
#: changedetectionio/templates/base.html:290
|
||||
msgid "Backups"
|
||||
msgstr "Backup"
|
||||
|
||||
@@ -1024,7 +1022,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
|
||||
msgid "# Watches"
|
||||
msgstr ""
|
||||
msgstr "# Monitoraggi"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
|
||||
msgid "Tag / Label name"
|
||||
@@ -1271,7 +1269,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
|
||||
msgid "Confirmation text"
|
||||
msgstr ""
|
||||
msgstr "Testo di conferma"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
|
||||
msgid "Type in the word"
|
||||
@@ -1290,7 +1288,8 @@ msgid "Clear History!"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:39
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:379
|
||||
#: changedetectionio/templates/base.html:399
|
||||
msgid "Cancel"
|
||||
msgstr "Annulla"
|
||||
|
||||
@@ -1320,11 +1319,11 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:53
|
||||
msgid "Words"
|
||||
msgstr ""
|
||||
msgstr "Parole"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:57
|
||||
msgid "Lines"
|
||||
msgstr ""
|
||||
msgstr "Righe"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:61
|
||||
msgid "Ignore Whitespace"
|
||||
@@ -1332,7 +1331,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:65
|
||||
msgid "Same/non-changed"
|
||||
msgstr ""
|
||||
msgstr "Uguale/non modificato"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:69
|
||||
msgid "Removed"
|
||||
@@ -1374,12 +1373,12 @@ msgstr ""
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:97
|
||||
#: changedetectionio/blueprint/ui/templates/preview.html:45
|
||||
msgid "Error Text"
|
||||
msgstr ""
|
||||
msgstr "Testo dell'errore"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:98
|
||||
#: changedetectionio/blueprint/ui/templates/preview.html:47
|
||||
msgid "Error Screenshot"
|
||||
msgstr ""
|
||||
msgstr "Screenshot dell'errore"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:99
|
||||
#: changedetectionio/blueprint/ui/templates/preview.html:50
|
||||
@@ -1389,7 +1388,7 @@ msgstr "Testo"
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:100
|
||||
#: changedetectionio/blueprint/ui/templates/preview.html:51
|
||||
msgid "Current screenshot"
|
||||
msgstr ""
|
||||
msgstr "Screenshot corrente"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:101
|
||||
msgid "Extract Data"
|
||||
@@ -1444,7 +1443,7 @@ msgstr ""
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:149
|
||||
#: changedetectionio/blueprint/ui/templates/preview.html:86
|
||||
msgid "Current screenshot from most recent request"
|
||||
msgstr ""
|
||||
msgstr "Screenshot corrente dalla richiesta più recente"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:151
|
||||
#: changedetectionio/blueprint/ui/templates/preview.html:88
|
||||
@@ -1912,7 +1911,7 @@ msgstr "Monitora questo URL!"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
|
||||
msgid "Edit first then Watch"
|
||||
msgstr "Modifica prima poi Monitora"
|
||||
msgstr "Modifica > Monitora"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
|
||||
msgid "Create a shareable link"
|
||||
@@ -2022,7 +2021,7 @@ msgstr "Modifica"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:130
|
||||
msgid "No website watches configured, please add a URL in the box above, or"
|
||||
msgstr ""
|
||||
msgstr "Nessun monitoraggio configurato, aggiungi un URL nella casella sopra, oppure"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:130
|
||||
msgid "import a list"
|
||||
@@ -2049,7 +2048,7 @@ msgid "No information"
|
||||
msgstr "Nessuna informazione"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:234
|
||||
#: changedetectionio/templates/base.html:248
|
||||
#: changedetectionio/templates/base.html:353
|
||||
msgid "Checking now"
|
||||
msgstr "Controllo in corso"
|
||||
|
||||
@@ -2116,7 +2115,9 @@ msgstr "Valore riquadro di selezione troppo lungo"
|
||||
|
||||
#: changedetectionio/processors/image_ssim_diff/forms.py:23
|
||||
msgid "Bounding box must be in format: x,y,width,height (integers only)"
|
||||
msgstr "Il riquadro deve essere nel formato: x,y,larghezza,altezza (solo numeri interi)"
|
||||
msgstr ""
|
||||
"Il riquadro deve essere nel formato: x,y,larghezza,altezza (solo numeri "
|
||||
"interi)"
|
||||
|
||||
#: changedetectionio/processors/image_ssim_diff/forms.py:29
|
||||
msgid "Bounding box values must be non-negative"
|
||||
@@ -2169,7 +2170,9 @@ msgstr "Rilevamento modifiche screenshot visivi"
|
||||
|
||||
#: changedetectionio/processors/image_ssim_diff/processor.py:22
|
||||
msgid "Compares screenshots using fast OpenCV algorithm, 10-100x faster than SSIM"
|
||||
msgstr "Confronta screenshot con algoritmo OpenCV veloce, 10-100x più veloce di SSIM"
|
||||
msgstr ""
|
||||
"Confronta screenshot con algoritmo OpenCV veloce, 10-100x più veloce di "
|
||||
"SSIM"
|
||||
|
||||
#: changedetectionio/processors/restock_diff/forms.py:15
|
||||
msgid "Re-stock detection"
|
||||
@@ -2233,59 +2236,233 @@ msgstr "Modifiche testo/HTML, JSON e PDF"
|
||||
msgid "Detects all text changes where possible"
|
||||
msgstr "Rileva tutte le modifiche di testo possibili"
|
||||
|
||||
#: changedetectionio/templates/base.html:77
|
||||
#: changedetectionio/templates/_helpers.html:25
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:153
|
||||
#, fuzzy
|
||||
msgid "Actions"
|
||||
msgstr "Condizioni"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:172
|
||||
msgid "Add a row/rule after"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:173
|
||||
msgid "Remove this row/rule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:174
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but "
|
||||
"Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"very affordable subscription based service which has all this setup for "
|
||||
"you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "You may need to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "Enable playwright environment variable"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "and uncomment the"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "in the"
|
||||
msgstr "Silenzia"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "file"
|
||||
msgstr "Titolo"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:240
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:247
|
||||
#, fuzzy
|
||||
msgid "Schedule time limits"
|
||||
msgstr "Tempo di ricontrollo (minuti)"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:248
|
||||
msgid "Business hours"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:249
|
||||
#, fuzzy
|
||||
msgid "Weekends"
|
||||
msgstr "Settimane"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:250
|
||||
#, fuzzy
|
||||
msgid "Reset"
|
||||
msgstr "Richiesta"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:259
|
||||
msgid ""
|
||||
"Warning, one or more of your 'days' has a duration that would extend into"
|
||||
" the next day."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:260
|
||||
msgid "This could have unintended consequences."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:270
|
||||
msgid "More help and examples about using the scheduler"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
#, fuzzy
|
||||
msgid "Want to use a time schedule?"
|
||||
msgstr "Usa pianificazione oraria"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
msgid "First confirm/save your Time Zone Settings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
msgid ""
|
||||
"Triggers a change if this text appears, AND something changed in the "
|
||||
"document."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
#, fuzzy
|
||||
msgid "Triggered text"
|
||||
msgstr "Ignora testo"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
msgid "Ignored for calculating changes, but still shown."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
#, fuzzy
|
||||
msgid "Ignored text"
|
||||
msgstr "Ignora testo"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "No change-detection will occur because this text exists."
|
||||
msgstr "Blocca rilevamento modifiche quando il testo corrisponde"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "Blocked text"
|
||||
msgstr "Ignora testo"
|
||||
|
||||
#: changedetectionio/templates/base.html:78
|
||||
#: changedetectionio/templates/base.html:168
|
||||
msgid "GROUPS"
|
||||
msgstr "GRUPPI"
|
||||
|
||||
#: changedetectionio/templates/base.html:80
|
||||
#: changedetectionio/templates/base.html:81
|
||||
#: changedetectionio/templates/base.html:169
|
||||
msgid "SETTINGS"
|
||||
msgstr "IMPOSTAZIONI"
|
||||
|
||||
#: changedetectionio/templates/base.html:83
|
||||
#: changedetectionio/templates/base.html:84
|
||||
#: changedetectionio/templates/base.html:170
|
||||
msgid "IMPORT"
|
||||
msgstr "IMPORTA"
|
||||
|
||||
#: changedetectionio/templates/base.html:86
|
||||
#: changedetectionio/templates/base.html:87
|
||||
#: changedetectionio/templates/base.html:171
|
||||
msgid "BACKUPS"
|
||||
msgstr "BACKUP"
|
||||
|
||||
#: changedetectionio/templates/base.html:90
|
||||
#: changedetectionio/templates/base.html:91
|
||||
#: changedetectionio/templates/base.html:173
|
||||
msgid "EDIT"
|
||||
msgstr "MODIFICA"
|
||||
|
||||
#: changedetectionio/templates/base.html:100
|
||||
#: changedetectionio/templates/base.html:101
|
||||
#: changedetectionio/templates/base.html:177
|
||||
msgid "LOG OUT"
|
||||
msgstr "ESCI"
|
||||
|
||||
#: changedetectionio/templates/base.html:109
|
||||
#: changedetectionio/templates/base.html:108
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "Cerca, o usa il tasto Alt+S"
|
||||
|
||||
#: changedetectionio/templates/base.html:116
|
||||
#: changedetectionio/templates/base.html:114
|
||||
msgid "Toggle Light/Dark Mode"
|
||||
msgstr "Cambia Modalità Chiaro/Scuro"
|
||||
|
||||
#: changedetectionio/templates/base.html:117
|
||||
#: changedetectionio/templates/base.html:115
|
||||
msgid "Toggle light/dark mode"
|
||||
msgstr "Cambia modalità chiaro/scuro"
|
||||
|
||||
#: changedetectionio/templates/base.html:127
|
||||
#: changedetectionio/templates/base.html:125
|
||||
msgid "Change Language"
|
||||
msgstr "Cambia Lingua"
|
||||
|
||||
#: changedetectionio/templates/base.html:128
|
||||
#: changedetectionio/templates/base.html:126
|
||||
msgid "Change language"
|
||||
msgstr "Cambia lingua"
|
||||
|
||||
#: changedetectionio/templates/base.html:249
|
||||
#: changedetectionio/templates/base.html:253
|
||||
#, fuzzy
|
||||
msgid "Watch List"
|
||||
msgstr "Lista Monitoraggi"
|
||||
|
||||
#: changedetectionio/templates/base.html:258
|
||||
#, fuzzy
|
||||
msgid "Watches"
|
||||
msgstr "Monitoraggi"
|
||||
|
||||
#: changedetectionio/templates/base.html:261
|
||||
msgid "Queue Status"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#, fuzzy
|
||||
msgid "Queue"
|
||||
msgstr "In coda"
|
||||
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:279
|
||||
#, fuzzy
|
||||
msgid "Settings"
|
||||
msgstr "IMPOSTAZIONI"
|
||||
|
||||
#: changedetectionio/templates/base.html:293
|
||||
msgid "Sitemap Crawler"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:318
|
||||
msgid "Sitemap"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:354
|
||||
msgid "Real-time updates offline"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:259
|
||||
#: changedetectionio/templates/base.html:364
|
||||
msgid "Select Language"
|
||||
msgstr "Seleziona Lingua"
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#: changedetectionio/templates/base.html:375
|
||||
msgid ""
|
||||
"Language support is in beta, please help us improve by opening a PR on "
|
||||
"GitHub with any updates."
|
||||
@@ -2293,11 +2470,30 @@ msgstr ""
|
||||
"Il supporto linguistico è in versione beta, aiutaci a migliorare aprendo "
|
||||
"una PR su GitHub con eventuali aggiornamenti."
|
||||
|
||||
#: changedetectionio/templates/login.html:10
|
||||
#: changedetectionio/templates/base.html:387
|
||||
#: changedetectionio/templates/base.html:400
|
||||
#, fuzzy
|
||||
msgid "Search"
|
||||
msgstr "Ricerca in corso"
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
msgid "URL or Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
#, fuzzy
|
||||
msgid "in"
|
||||
msgstr "Info"
|
||||
|
||||
#: changedetectionio/templates/base.html:393
|
||||
msgid "Enter search term..."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:11
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: changedetectionio/templates/login.html:16
|
||||
#: changedetectionio/templates/login.html:17
|
||||
msgid "Login"
|
||||
msgstr "Accedi"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-01-02 16:07+0100\n"
|
||||
"POT-Creation-Date: 2026-01-03 14:31+0100\n"
|
||||
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ko\n"
|
||||
@@ -19,21 +19,21 @@ msgstr ""
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:241
|
||||
#: changedetectionio/flask_app.py:213 changedetectionio/flask_app.py:225
|
||||
#: changedetectionio/flask_app.py:246
|
||||
#: changedetectionio/flask_app.py:214 changedetectionio/flask_app.py:226
|
||||
#: changedetectionio/flask_app.py:247
|
||||
#: changedetectionio/realtime/socket_server.py:171
|
||||
msgid "Not yet"
|
||||
msgstr "아직 아님"
|
||||
|
||||
#: changedetectionio/flask_app.py:468
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:495
|
||||
#: changedetectionio/flask_app.py:534
|
||||
msgid "Already logged in"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:522
|
||||
#: changedetectionio/flask_app.py:536
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:551
|
||||
#, fuzzy
|
||||
msgid "Incorrect password"
|
||||
msgstr "비밀번호"
|
||||
@@ -176,18 +176,16 @@ msgid "Invalid value."
|
||||
msgstr "값이 잘못되었습니다."
|
||||
|
||||
#: changedetectionio/forms.py:732
|
||||
#, fuzzy
|
||||
msgid "Watch"
|
||||
msgstr "# 시계"
|
||||
msgstr "모니터"
|
||||
|
||||
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
|
||||
msgid "Processor"
|
||||
msgstr "프로세서"
|
||||
|
||||
#: changedetectionio/forms.py:734
|
||||
#, fuzzy
|
||||
msgid "Edit > Watch"
|
||||
msgstr "먼저 편집한 다음 보기"
|
||||
msgstr "편집 > 모니터"
|
||||
|
||||
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
|
||||
#, fuzzy
|
||||
@@ -363,7 +361,7 @@ msgstr "구하다"
|
||||
|
||||
#: changedetectionio/forms.py:829
|
||||
msgid "Proxy"
|
||||
msgstr "대리"
|
||||
msgstr "프록시"
|
||||
|
||||
#: changedetectionio/forms.py:831
|
||||
msgid "Send a notification when the filter can no longer be found on the page"
|
||||
@@ -374,7 +372,7 @@ msgstr "페이지에서 필터를 더 이상 찾을 수 없으면 알림 보내
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:59
|
||||
#: changedetectionio/forms.py:832
|
||||
msgid "Notifications"
|
||||
msgstr "정보 없음"
|
||||
msgstr "알림"
|
||||
|
||||
#: changedetectionio/forms.py:832
|
||||
#, fuzzy
|
||||
@@ -427,9 +425,8 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
|
||||
#, fuzzy
|
||||
msgid "Name"
|
||||
msgstr "음소거 해제"
|
||||
msgstr "이름"
|
||||
|
||||
#: changedetectionio/forms.py:921
|
||||
msgid "Proxy URL"
|
||||
@@ -452,9 +449,8 @@ msgid "Plaintext requests"
|
||||
msgstr "일반 텍스트 요청"
|
||||
|
||||
#: changedetectionio/forms.py:946
|
||||
#, fuzzy
|
||||
msgid "Chrome requests"
|
||||
msgstr "요구"
|
||||
msgstr "Chrome 요청"
|
||||
|
||||
#: changedetectionio/forms.py:952
|
||||
msgid "Default proxy"
|
||||
@@ -511,9 +507,8 @@ msgid "API access token security check enabled"
|
||||
msgstr "API 액세스 토큰 보안 확인이 활성화되었습니다."
|
||||
|
||||
#: changedetectionio/forms.py:989
|
||||
#, fuzzy
|
||||
msgid "Notification base URL override"
|
||||
msgstr "알림 경고 수"
|
||||
msgstr "알림 기본 URL"
|
||||
|
||||
#: changedetectionio/forms.py:993
|
||||
msgid "Treat empty pages as a change?"
|
||||
@@ -622,6 +617,8 @@ msgid "Backups were deleted."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:6
|
||||
#: changedetectionio/templates/base.html:282
|
||||
#: changedetectionio/templates/base.html:290
|
||||
msgid "Backups"
|
||||
msgstr "백업"
|
||||
|
||||
@@ -645,11 +642,11 @@ msgstr "백업을 찾을 수 없습니다."
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:28
|
||||
msgid "Create backup"
|
||||
msgstr "백업"
|
||||
msgstr "백업 생성"
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:30
|
||||
msgid "Remove backups"
|
||||
msgstr "백업"
|
||||
msgstr "백업 삭제"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py:45
|
||||
msgid ""
|
||||
@@ -857,7 +854,7 @@ msgstr "일반적인"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:23
|
||||
msgid "Fetching"
|
||||
msgstr "수색"
|
||||
msgstr "가져오기"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:24
|
||||
msgid "Global Filters"
|
||||
@@ -885,7 +882,7 @@ msgstr "보안 문자 및 프록시"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:35
|
||||
msgid "Info"
|
||||
msgstr "추가 정보"
|
||||
msgstr "정보"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:46
|
||||
msgid "Default recheck time for all watches, current system minimum is"
|
||||
@@ -913,7 +910,7 @@ msgstr "활성화된 플러그인이 없습니다."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:405
|
||||
msgid "Back"
|
||||
msgstr "백업"
|
||||
msgstr "뒤로"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:406
|
||||
msgid "Clear Snapshot History"
|
||||
@@ -1036,7 +1033,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
|
||||
msgid "# Watches"
|
||||
msgstr "# 시계"
|
||||
msgstr "# 모니터"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
|
||||
msgid "Tag / Label name"
|
||||
@@ -1288,7 +1285,7 @@ msgstr "먼저 링크하세요."
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
|
||||
msgid "Confirmation text"
|
||||
msgstr "정보 없음"
|
||||
msgstr "확인 텍스트"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
|
||||
msgid "Type in the word"
|
||||
@@ -1307,7 +1304,8 @@ msgid "Clear History!"
|
||||
msgstr "기록 지우기"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:39
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:379
|
||||
#: changedetectionio/templates/base.html:399
|
||||
msgid "Cancel"
|
||||
msgstr "취소"
|
||||
|
||||
@@ -1337,11 +1335,11 @@ msgstr "에게"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:53
|
||||
msgid "Words"
|
||||
msgstr "비밀번호"
|
||||
msgstr "단어"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:57
|
||||
msgid "Lines"
|
||||
msgstr "로그인"
|
||||
msgstr "줄"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:61
|
||||
msgid "Ignore Whitespace"
|
||||
@@ -1349,7 +1347,7 @@ msgstr "공백 무시"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:65
|
||||
msgid "Same/non-changed"
|
||||
msgstr "변경됨"
|
||||
msgstr "동일/변경되지 않음"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html:69
|
||||
msgid "Removed"
|
||||
@@ -1490,7 +1488,7 @@ msgstr "정황"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:60
|
||||
msgid "Stats"
|
||||
msgstr "설정"
|
||||
msgstr "통계"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:73
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:313
|
||||
@@ -1925,11 +1923,11 @@ msgstr "새로운 웹 페이지 변경 감지 감시 추가"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
|
||||
msgid "Watch this URL!"
|
||||
msgstr "이 URL을 시청하세요!"
|
||||
msgstr "이 URL 모니터!"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
|
||||
msgid "Edit first then Watch"
|
||||
msgstr "먼저 편집한 다음 보기"
|
||||
msgstr "편집 후 모니터"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
|
||||
msgid "Create a shareable link"
|
||||
@@ -2066,7 +2064,7 @@ msgid "No information"
|
||||
msgstr "정보 없음"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:234
|
||||
#: changedetectionio/templates/base.html:248
|
||||
#: changedetectionio/templates/base.html:353
|
||||
msgid "Checking now"
|
||||
msgstr "지금 확인 중"
|
||||
|
||||
@@ -2076,7 +2074,7 @@ msgstr "대기 중"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:250
|
||||
msgid "History"
|
||||
msgstr "역사"
|
||||
msgstr "기록"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:251
|
||||
msgid "Preview"
|
||||
@@ -2252,69 +2250,264 @@ msgstr "웹페이지 텍스트/HTML, JSON 및 PDF 변경"
|
||||
msgid "Detects all text changes where possible"
|
||||
msgstr "가능한 경우 모든 텍스트 변경 사항을 감지합니다."
|
||||
|
||||
#: changedetectionio/templates/base.html:77
|
||||
#: changedetectionio/templates/_helpers.html:25
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:153
|
||||
#, fuzzy
|
||||
msgid "Actions"
|
||||
msgstr "정황"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:172
|
||||
msgid "Add a row/rule after"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:173
|
||||
msgid "Remove this row/rule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:174
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but "
|
||||
"Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"very affordable subscription based service which has all this setup for "
|
||||
"you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "You may need to"
|
||||
msgstr "당신은"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "Enable playwright environment variable"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "and uncomment the"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "in the"
|
||||
msgstr "그만큼"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "file"
|
||||
msgstr "제목"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:240
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:247
|
||||
#, fuzzy
|
||||
msgid "Schedule time limits"
|
||||
msgstr "재확인 시간(분)"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:248
|
||||
msgid "Business hours"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:249
|
||||
#, fuzzy
|
||||
msgid "Weekends"
|
||||
msgstr "주"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:250
|
||||
#, fuzzy
|
||||
msgid "Reset"
|
||||
msgstr "요구"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:259
|
||||
msgid ""
|
||||
"Warning, one or more of your 'days' has a duration that would extend into"
|
||||
" the next day."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:260
|
||||
msgid "This could have unintended consequences."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:270
|
||||
#, fuzzy
|
||||
msgid "More help and examples about using the scheduler"
|
||||
msgstr "여기에 더 많은 도움말과 예시가 있습니다."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
#, fuzzy
|
||||
msgid "Want to use a time schedule?"
|
||||
msgstr "시간 스케줄러 사용"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
msgid "First confirm/save your Time Zone Settings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
msgid ""
|
||||
"Triggers a change if this text appears, AND something changed in the "
|
||||
"document."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
#, fuzzy
|
||||
msgid "Triggered text"
|
||||
msgstr "오류 텍스트"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
msgid "Ignored for calculating changes, but still shown."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
#, fuzzy
|
||||
msgid "Ignored text"
|
||||
msgstr "오류 텍스트"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "No change-detection will occur because this text exists."
|
||||
msgstr "텍스트가 일치하는 동안 변경 감지 차단"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "Blocked text"
|
||||
msgstr "오류 텍스트"
|
||||
|
||||
#: changedetectionio/templates/base.html:78
|
||||
#: changedetectionio/templates/base.html:168
|
||||
msgid "GROUPS"
|
||||
msgstr "여러 떼"
|
||||
|
||||
#: changedetectionio/templates/base.html:80
|
||||
#: changedetectionio/templates/base.html:81
|
||||
#: changedetectionio/templates/base.html:169
|
||||
msgid "SETTINGS"
|
||||
msgstr "설정"
|
||||
|
||||
#: changedetectionio/templates/base.html:83
|
||||
#: changedetectionio/templates/base.html:84
|
||||
#: changedetectionio/templates/base.html:170
|
||||
msgid "IMPORT"
|
||||
msgstr "수입"
|
||||
msgstr "가져오기"
|
||||
|
||||
#: changedetectionio/templates/base.html:86
|
||||
#: changedetectionio/templates/base.html:87
|
||||
#: changedetectionio/templates/base.html:171
|
||||
msgid "BACKUPS"
|
||||
msgstr "백업"
|
||||
|
||||
#: changedetectionio/templates/base.html:90
|
||||
#: changedetectionio/templates/base.html:91
|
||||
#: changedetectionio/templates/base.html:173
|
||||
msgid "EDIT"
|
||||
msgstr "편집하다"
|
||||
|
||||
#: changedetectionio/templates/base.html:100
|
||||
#: changedetectionio/templates/base.html:101
|
||||
#: changedetectionio/templates/base.html:177
|
||||
msgid "LOG OUT"
|
||||
msgstr "로그아웃"
|
||||
|
||||
#: changedetectionio/templates/base.html:109
|
||||
#: changedetectionio/templates/base.html:108
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "검색 또는 Alt+S 키 사용"
|
||||
|
||||
#: changedetectionio/templates/base.html:116
|
||||
#: changedetectionio/templates/base.html:114
|
||||
msgid "Toggle Light/Dark Mode"
|
||||
msgstr "밝은/어두운 모드 전환"
|
||||
|
||||
#: changedetectionio/templates/base.html:117
|
||||
#: changedetectionio/templates/base.html:115
|
||||
msgid "Toggle light/dark mode"
|
||||
msgstr "밝은/어두운 모드 전환"
|
||||
|
||||
#: changedetectionio/templates/base.html:127
|
||||
#: changedetectionio/templates/base.html:125
|
||||
msgid "Change Language"
|
||||
msgstr "언어 변경"
|
||||
|
||||
#: changedetectionio/templates/base.html:128
|
||||
#: changedetectionio/templates/base.html:126
|
||||
msgid "Change language"
|
||||
msgstr "언어 변경"
|
||||
|
||||
#: changedetectionio/templates/base.html:249
|
||||
#: changedetectionio/templates/base.html:253
|
||||
#, fuzzy
|
||||
msgid "Watch List"
|
||||
msgstr "모니터 목록"
|
||||
|
||||
#: changedetectionio/templates/base.html:258
|
||||
#, fuzzy
|
||||
msgid "Watches"
|
||||
msgstr "모니터"
|
||||
|
||||
#: changedetectionio/templates/base.html:261
|
||||
msgid "Queue Status"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#, fuzzy
|
||||
msgid "Queue"
|
||||
msgstr "대기 중"
|
||||
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:279
|
||||
#, fuzzy
|
||||
msgid "Settings"
|
||||
msgstr "설정"
|
||||
|
||||
#: changedetectionio/templates/base.html:293
|
||||
msgid "Sitemap Crawler"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:318
|
||||
msgid "Sitemap"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:354
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "실시간 업데이트 오프라인"
|
||||
|
||||
#: changedetectionio/templates/base.html:259
|
||||
#: changedetectionio/templates/base.html:364
|
||||
msgid "Select Language"
|
||||
msgstr "언어 선택"
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#: changedetectionio/templates/base.html:375
|
||||
msgid ""
|
||||
"Language support is in beta, please help us improve by opening a PR on "
|
||||
"GitHub with any updates."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:10
|
||||
#: changedetectionio/templates/base.html:387
|
||||
#: changedetectionio/templates/base.html:400
|
||||
#, fuzzy
|
||||
msgid "Search"
|
||||
msgstr "수색"
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
msgid "URL or Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
#, fuzzy
|
||||
msgid "in"
|
||||
msgstr "추가 정보"
|
||||
|
||||
#: changedetectionio/templates/base.html:393
|
||||
msgid "Enter search term..."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:11
|
||||
msgid "Password"
|
||||
msgstr "비밀번호"
|
||||
|
||||
#: changedetectionio/templates/login.html:16
|
||||
#: changedetectionio/templates/login.html:17
|
||||
msgid "Login"
|
||||
msgstr "로그인"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-01-02 16:07+0100\n"
|
||||
"POT-Creation-Date: 2026-01-03 14:31+0100\n"
|
||||
"PO-Revision-Date: 2026-01-02 11:54+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -19,21 +19,21 @@ msgstr ""
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:241
|
||||
#: changedetectionio/flask_app.py:213 changedetectionio/flask_app.py:225
|
||||
#: changedetectionio/flask_app.py:246
|
||||
#: changedetectionio/flask_app.py:214 changedetectionio/flask_app.py:226
|
||||
#: changedetectionio/flask_app.py:247
|
||||
#: changedetectionio/realtime/socket_server.py:171
|
||||
msgid "Not yet"
|
||||
msgstr "还没有"
|
||||
|
||||
#: changedetectionio/flask_app.py:468
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:495
|
||||
#: changedetectionio/flask_app.py:534
|
||||
msgid "Already logged in"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:522
|
||||
#: changedetectionio/flask_app.py:536
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:551
|
||||
#, fuzzy
|
||||
msgid "Incorrect password"
|
||||
msgstr "密码"
|
||||
@@ -176,9 +176,8 @@ msgid "Invalid value."
|
||||
msgstr "无效值。"
|
||||
|
||||
#: changedetectionio/forms.py:732
|
||||
#, fuzzy
|
||||
msgid "Watch"
|
||||
msgstr "# 手表"
|
||||
msgstr "监控"
|
||||
|
||||
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
|
||||
msgid "Processor"
|
||||
@@ -187,7 +186,7 @@ msgstr "处理器"
|
||||
#: changedetectionio/forms.py:734
|
||||
#, fuzzy
|
||||
msgid "Edit > Watch"
|
||||
msgstr "先编辑后观看"
|
||||
msgstr "编辑 > 监控"
|
||||
|
||||
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
|
||||
#, fuzzy
|
||||
@@ -363,7 +362,7 @@ msgstr "节省"
|
||||
|
||||
#: changedetectionio/forms.py:829
|
||||
msgid "Proxy"
|
||||
msgstr "代理人"
|
||||
msgstr "代理"
|
||||
|
||||
#: changedetectionio/forms.py:831
|
||||
msgid "Send a notification when the filter can no longer be found on the page"
|
||||
@@ -374,7 +373,7 @@ msgstr "当页面上找不到过滤器时发送通知"
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:59
|
||||
#: changedetectionio/forms.py:832
|
||||
msgid "Notifications"
|
||||
msgstr "暂无信息"
|
||||
msgstr "通知"
|
||||
|
||||
#: changedetectionio/forms.py:832
|
||||
#, fuzzy
|
||||
@@ -427,9 +426,8 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
|
||||
#, fuzzy
|
||||
msgid "Name"
|
||||
msgstr "取消静音"
|
||||
msgstr "名称"
|
||||
|
||||
#: changedetectionio/forms.py:921
|
||||
msgid "Proxy URL"
|
||||
@@ -452,9 +450,8 @@ msgid "Plaintext requests"
|
||||
msgstr "明文请求"
|
||||
|
||||
#: changedetectionio/forms.py:946
|
||||
#, fuzzy
|
||||
msgid "Chrome requests"
|
||||
msgstr "要求"
|
||||
msgstr "Chrome请求"
|
||||
|
||||
#: changedetectionio/forms.py:952
|
||||
msgid "Default proxy"
|
||||
@@ -511,9 +508,8 @@ msgid "API access token security check enabled"
|
||||
msgstr "已启用 API 访问令牌安全检查"
|
||||
|
||||
#: changedetectionio/forms.py:989
|
||||
#, fuzzy
|
||||
msgid "Notification base URL override"
|
||||
msgstr "通知警报计数"
|
||||
msgstr "通知基础URL"
|
||||
|
||||
#: changedetectionio/forms.py:993
|
||||
msgid "Treat empty pages as a change?"
|
||||
@@ -622,6 +618,8 @@ msgid "Backups were deleted."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:6
|
||||
#: changedetectionio/templates/base.html:282
|
||||
#: changedetectionio/templates/base.html:290
|
||||
msgid "Backups"
|
||||
msgstr "备份"
|
||||
|
||||
@@ -645,11 +643,11 @@ msgstr "未找到备份。"
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:28
|
||||
msgid "Create backup"
|
||||
msgstr "备份"
|
||||
msgstr "创建备份"
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:30
|
||||
msgid "Remove backups"
|
||||
msgstr "备份"
|
||||
msgstr "删除备份"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py:45
|
||||
msgid ""
|
||||
@@ -857,7 +855,7 @@ msgstr "一般的"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:23
|
||||
msgid "Fetching"
|
||||
msgstr "搜寻中"
|
||||
msgstr "获取"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:24
|
||||
msgid "Global Filters"
|
||||
@@ -885,7 +883,7 @@ msgstr "验证码和代理"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:35
|
||||
msgid "Info"
|
||||
msgstr "更多信息"
|
||||
msgstr "信息"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:46
|
||||
msgid "Default recheck time for all watches, current system minimum is"
|
||||
@@ -913,7 +911,7 @@ msgstr "没有激活的插件"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:405
|
||||
msgid "Back"
|
||||
msgstr "备份"
|
||||
msgstr "返回"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html:406
|
||||
msgid "Clear Snapshot History"
|
||||
@@ -1036,7 +1034,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
|
||||
msgid "# Watches"
|
||||
msgstr "# 手表"
|
||||
msgstr "# 监控项"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
|
||||
msgid "Tag / Label name"
|
||||
@@ -1288,7 +1286,7 @@ msgstr "先链接。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:17
|
||||
msgid "Confirmation text"
|
||||
msgstr "暂无信息"
|
||||
msgstr "确认文本"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:27
|
||||
msgid "Type in the word"
|
||||
@@ -1307,7 +1305,8 @@ msgid "Clear History!"
|
||||
msgstr "清晰的历史记录"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:39
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:379
|
||||
#: changedetectionio/templates/base.html:399
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
@@ -1490,7 +1489,7 @@ msgstr "状况"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:60
|
||||
msgid "Stats"
|
||||
msgstr "设置"
|
||||
msgstr "统计"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:73
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html:313
|
||||
@@ -1925,11 +1924,11 @@ msgstr "添加新的网页更改检测监视"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
|
||||
msgid "Watch this URL!"
|
||||
msgstr "关注这个网址!"
|
||||
msgstr "监控此URL!"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
|
||||
msgid "Edit first then Watch"
|
||||
msgstr "先编辑后观看"
|
||||
msgstr "编辑后监控"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
|
||||
msgid "Create a shareable link"
|
||||
@@ -2066,7 +2065,7 @@ msgid "No information"
|
||||
msgstr "暂无信息"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:234
|
||||
#: changedetectionio/templates/base.html:248
|
||||
#: changedetectionio/templates/base.html:353
|
||||
msgid "Checking now"
|
||||
msgstr "立即检查"
|
||||
|
||||
@@ -2252,69 +2251,264 @@ msgstr "网页文本/HTML、JSON 和 PDF 更改"
|
||||
msgid "Detects all text changes where possible"
|
||||
msgstr "尽可能检测所有文本更改"
|
||||
|
||||
#: changedetectionio/templates/base.html:77
|
||||
#: changedetectionio/templates/_helpers.html:25
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:153
|
||||
#, fuzzy
|
||||
msgid "Actions"
|
||||
msgstr "状况"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:172
|
||||
msgid "Add a row/rule after"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:173
|
||||
msgid "Remove this row/rule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:174
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but "
|
||||
"Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"very affordable subscription based service which has all this setup for "
|
||||
"you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "You may need to"
|
||||
msgstr "你需要"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "Enable playwright environment variable"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "and uncomment the"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "in the"
|
||||
msgstr "这"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "file"
|
||||
msgstr "标题"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:240
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:247
|
||||
#, fuzzy
|
||||
msgid "Schedule time limits"
|
||||
msgstr "复检时间(分钟)"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:248
|
||||
msgid "Business hours"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:249
|
||||
#, fuzzy
|
||||
msgid "Weekends"
|
||||
msgstr "周数"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:250
|
||||
#, fuzzy
|
||||
msgid "Reset"
|
||||
msgstr "要求"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:259
|
||||
msgid ""
|
||||
"Warning, one or more of your 'days' has a duration that would extend into"
|
||||
" the next day."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:260
|
||||
msgid "This could have unintended consequences."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:270
|
||||
#, fuzzy
|
||||
msgid "More help and examples about using the scheduler"
|
||||
msgstr "更多帮助和示例请参见此处"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
#, fuzzy
|
||||
msgid "Want to use a time schedule?"
|
||||
msgstr "使用时间调度器"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
msgid "First confirm/save your Time Zone Settings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
msgid ""
|
||||
"Triggers a change if this text appears, AND something changed in the "
|
||||
"document."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
#, fuzzy
|
||||
msgid "Triggered text"
|
||||
msgstr "错误文本"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
msgid "Ignored for calculating changes, but still shown."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
#, fuzzy
|
||||
msgid "Ignored text"
|
||||
msgstr "错误文本"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "No change-detection will occur because this text exists."
|
||||
msgstr "文本匹配时阻止更改检测"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "Blocked text"
|
||||
msgstr "错误文本"
|
||||
|
||||
#: changedetectionio/templates/base.html:78
|
||||
#: changedetectionio/templates/base.html:168
|
||||
msgid "GROUPS"
|
||||
msgstr "团体"
|
||||
|
||||
#: changedetectionio/templates/base.html:80
|
||||
#: changedetectionio/templates/base.html:81
|
||||
#: changedetectionio/templates/base.html:169
|
||||
msgid "SETTINGS"
|
||||
msgstr "设置"
|
||||
|
||||
#: changedetectionio/templates/base.html:83
|
||||
#: changedetectionio/templates/base.html:84
|
||||
#: changedetectionio/templates/base.html:170
|
||||
msgid "IMPORT"
|
||||
msgstr "进口"
|
||||
msgstr "导入"
|
||||
|
||||
#: changedetectionio/templates/base.html:86
|
||||
#: changedetectionio/templates/base.html:87
|
||||
#: changedetectionio/templates/base.html:171
|
||||
msgid "BACKUPS"
|
||||
msgstr "备份"
|
||||
|
||||
#: changedetectionio/templates/base.html:90
|
||||
#: changedetectionio/templates/base.html:91
|
||||
#: changedetectionio/templates/base.html:173
|
||||
msgid "EDIT"
|
||||
msgstr "编辑"
|
||||
|
||||
#: changedetectionio/templates/base.html:100
|
||||
#: changedetectionio/templates/base.html:101
|
||||
#: changedetectionio/templates/base.html:177
|
||||
msgid "LOG OUT"
|
||||
msgstr "退出"
|
||||
|
||||
#: changedetectionio/templates/base.html:109
|
||||
#: changedetectionio/templates/base.html:108
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "搜索或使用 Alt+S 键"
|
||||
|
||||
#: changedetectionio/templates/base.html:116
|
||||
#: changedetectionio/templates/base.html:114
|
||||
msgid "Toggle Light/Dark Mode"
|
||||
msgstr "切换亮/暗模式"
|
||||
|
||||
#: changedetectionio/templates/base.html:117
|
||||
#: changedetectionio/templates/base.html:115
|
||||
msgid "Toggle light/dark mode"
|
||||
msgstr "切换亮/暗模式"
|
||||
|
||||
#: changedetectionio/templates/base.html:127
|
||||
#: changedetectionio/templates/base.html:125
|
||||
msgid "Change Language"
|
||||
msgstr "更改语言"
|
||||
|
||||
#: changedetectionio/templates/base.html:128
|
||||
#: changedetectionio/templates/base.html:126
|
||||
msgid "Change language"
|
||||
msgstr "更改语言"
|
||||
|
||||
#: changedetectionio/templates/base.html:249
|
||||
#: changedetectionio/templates/base.html:253
|
||||
#, fuzzy
|
||||
msgid "Watch List"
|
||||
msgstr "监控列表"
|
||||
|
||||
#: changedetectionio/templates/base.html:258
|
||||
#, fuzzy
|
||||
msgid "Watches"
|
||||
msgstr "监控项"
|
||||
|
||||
#: changedetectionio/templates/base.html:261
|
||||
msgid "Queue Status"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#, fuzzy
|
||||
msgid "Queue"
|
||||
msgstr "排队"
|
||||
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:279
|
||||
#, fuzzy
|
||||
msgid "Settings"
|
||||
msgstr "设置"
|
||||
|
||||
#: changedetectionio/templates/base.html:293
|
||||
msgid "Sitemap Crawler"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:318
|
||||
msgid "Sitemap"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:354
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "离线实时更新"
|
||||
|
||||
#: changedetectionio/templates/base.html:259
|
||||
#: changedetectionio/templates/base.html:364
|
||||
msgid "Select Language"
|
||||
msgstr "选择语言"
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#: changedetectionio/templates/base.html:375
|
||||
msgid ""
|
||||
"Language support is in beta, please help us improve by opening a PR on "
|
||||
"GitHub with any updates."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:10
|
||||
#: changedetectionio/templates/base.html:387
|
||||
#: changedetectionio/templates/base.html:400
|
||||
#, fuzzy
|
||||
msgid "Search"
|
||||
msgstr "搜寻中"
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
msgid "URL or Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
#, fuzzy
|
||||
msgid "in"
|
||||
msgstr "更多信息"
|
||||
|
||||
#: changedetectionio/templates/base.html:393
|
||||
msgid "Enter search term..."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:11
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: changedetectionio/templates/login.html:16
|
||||
#: changedetectionio/templates/login.html:17
|
||||
msgid "Login"
|
||||
msgstr "登录"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-01-02 16:07+0100\n"
|
||||
"POT-Creation-Date: 2026-01-03 14:31+0100\n"
|
||||
"PO-Revision-Date: 2026-01-02 12:37+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh_Hant_TW\n"
|
||||
@@ -19,21 +19,21 @@ msgstr ""
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:241
|
||||
#: changedetectionio/flask_app.py:213 changedetectionio/flask_app.py:225
|
||||
#: changedetectionio/flask_app.py:246
|
||||
#: changedetectionio/flask_app.py:214 changedetectionio/flask_app.py:226
|
||||
#: changedetectionio/flask_app.py:247
|
||||
#: changedetectionio/realtime/socket_server.py:171
|
||||
msgid "Not yet"
|
||||
msgstr "還沒有"
|
||||
|
||||
#: changedetectionio/flask_app.py:468
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:495
|
||||
#: changedetectionio/flask_app.py:534
|
||||
msgid "Already logged in"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:522
|
||||
#: changedetectionio/flask_app.py:536
|
||||
msgid "You must be logged in, please log in."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/flask_app.py:551
|
||||
#, fuzzy
|
||||
msgid "Incorrect password"
|
||||
msgstr "密碼"
|
||||
@@ -176,18 +176,16 @@ msgid "Invalid value."
|
||||
msgstr "無效值。"
|
||||
|
||||
#: changedetectionio/forms.py:732
|
||||
#, fuzzy
|
||||
msgid "Watch"
|
||||
msgstr "# 手錶"
|
||||
msgstr "監控"
|
||||
|
||||
#: changedetectionio/forms.py:733 changedetectionio/forms.py:766
|
||||
msgid "Processor"
|
||||
msgstr "處理器"
|
||||
|
||||
#: changedetectionio/forms.py:734
|
||||
#, fuzzy
|
||||
msgid "Edit > Watch"
|
||||
msgstr "先編輯後觀看"
|
||||
msgstr "編輯 > 監控"
|
||||
|
||||
#: changedetectionio/forms.py:747 changedetectionio/forms.py:994
|
||||
#, fuzzy
|
||||
@@ -363,7 +361,7 @@ msgstr "節省"
|
||||
|
||||
#: changedetectionio/forms.py:829
|
||||
msgid "Proxy"
|
||||
msgstr "代理人"
|
||||
msgstr "代理"
|
||||
|
||||
#: changedetectionio/forms.py:831
|
||||
msgid "Send a notification when the filter can no longer be found on the page"
|
||||
@@ -427,9 +425,8 @@ msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py:920 changedetectionio/forms.py:932
|
||||
#, fuzzy
|
||||
msgid "Name"
|
||||
msgstr "取消靜音"
|
||||
msgstr "名稱"
|
||||
|
||||
#: changedetectionio/forms.py:921
|
||||
msgid "Proxy URL"
|
||||
@@ -452,9 +449,8 @@ msgid "Plaintext requests"
|
||||
msgstr "明文請求"
|
||||
|
||||
#: changedetectionio/forms.py:946
|
||||
#, fuzzy
|
||||
msgid "Chrome requests"
|
||||
msgstr "要求"
|
||||
msgstr "Chrome請求"
|
||||
|
||||
#: changedetectionio/forms.py:952
|
||||
msgid "Default proxy"
|
||||
@@ -511,9 +507,8 @@ msgid "API access token security check enabled"
|
||||
msgstr "已啟用 API 訪問令牌安全檢查"
|
||||
|
||||
#: changedetectionio/forms.py:989
|
||||
#, fuzzy
|
||||
msgid "Notification base URL override"
|
||||
msgstr "通知警報計數"
|
||||
msgstr "通知基礎URL"
|
||||
|
||||
#: changedetectionio/forms.py:993
|
||||
msgid "Treat empty pages as a change?"
|
||||
@@ -622,6 +617,8 @@ msgid "Backups were deleted."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/overview.html:6
|
||||
#: changedetectionio/templates/base.html:282
|
||||
#: changedetectionio/templates/base.html:290
|
||||
msgid "Backups"
|
||||
msgstr "備份"
|
||||
|
||||
@@ -1036,7 +1033,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:31
|
||||
msgid "# Watches"
|
||||
msgstr "# 手錶"
|
||||
msgstr "# 監控項"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html:32
|
||||
msgid "Tag / Label name"
|
||||
@@ -1307,7 +1304,8 @@ msgid "Clear History!"
|
||||
msgstr "清除歷史!"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html:39
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:379
|
||||
#: changedetectionio/templates/base.html:399
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
@@ -1925,11 +1923,11 @@ msgstr "添加新的網頁更改檢測監視"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:34
|
||||
msgid "Watch this URL!"
|
||||
msgstr "關注這個網址!"
|
||||
msgstr "監控此URL!"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:35
|
||||
msgid "Edit first then Watch"
|
||||
msgstr "先編輯後觀看"
|
||||
msgstr "編輯後監控"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:45
|
||||
msgid "Create a shareable link"
|
||||
@@ -2066,7 +2064,7 @@ msgid "No information"
|
||||
msgstr "暫無信息"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html:234
|
||||
#: changedetectionio/templates/base.html:248
|
||||
#: changedetectionio/templates/base.html:353
|
||||
msgid "Checking now"
|
||||
msgstr "立即檢查"
|
||||
|
||||
@@ -2252,69 +2250,264 @@ msgstr "網頁文本/HTML、JSON 和 PDF 更改"
|
||||
msgid "Detects all text changes where possible"
|
||||
msgstr "盡可能檢測所有文本更改"
|
||||
|
||||
#: changedetectionio/templates/base.html:77
|
||||
#: changedetectionio/templates/_helpers.html:25
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:153
|
||||
#, fuzzy
|
||||
msgid "Actions"
|
||||
msgstr "狀況"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:172
|
||||
msgid "Add a row/rule after"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:173
|
||||
msgid "Remove this row/rule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:174
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but "
|
||||
"Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:184
|
||||
msgid ""
|
||||
"very affordable subscription based service which has all this setup for "
|
||||
"you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "You may need to"
|
||||
msgstr "你需要"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "Enable playwright environment variable"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
msgid "and uncomment the"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "in the"
|
||||
msgstr "這"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:185
|
||||
#, fuzzy
|
||||
msgid "file"
|
||||
msgstr "標題"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:240
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:247
|
||||
#, fuzzy
|
||||
msgid "Schedule time limits"
|
||||
msgstr "複檢時間(分鐘)"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:248
|
||||
msgid "Business hours"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:249
|
||||
#, fuzzy
|
||||
msgid "Weekends"
|
||||
msgstr "週數"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:250
|
||||
#, fuzzy
|
||||
msgid "Reset"
|
||||
msgstr "要求"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:259
|
||||
msgid ""
|
||||
"Warning, one or more of your 'days' has a duration that would extend into"
|
||||
" the next day."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:260
|
||||
msgid "This could have unintended consequences."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:270
|
||||
#, fuzzy
|
||||
msgid "More help and examples about using the scheduler"
|
||||
msgstr "更多幫助和示例請參見此處"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
#, fuzzy
|
||||
msgid "Want to use a time schedule?"
|
||||
msgstr "使用時間調度器"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:275
|
||||
msgid "First confirm/save your Time Zone Settings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
msgid ""
|
||||
"Triggers a change if this text appears, AND something changed in the "
|
||||
"document."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:284
|
||||
#, fuzzy
|
||||
msgid "Triggered text"
|
||||
msgstr "錯誤文本"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
msgid "Ignored for calculating changes, but still shown."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:285
|
||||
#, fuzzy
|
||||
msgid "Ignored text"
|
||||
msgstr "錯誤文本"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "No change-detection will occur because this text exists."
|
||||
msgstr "文本匹配時阻止更改檢測"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html:286
|
||||
#, fuzzy
|
||||
msgid "Blocked text"
|
||||
msgstr "錯誤文本"
|
||||
|
||||
#: changedetectionio/templates/base.html:78
|
||||
#: changedetectionio/templates/base.html:168
|
||||
msgid "GROUPS"
|
||||
msgstr "團體"
|
||||
|
||||
#: changedetectionio/templates/base.html:80
|
||||
#: changedetectionio/templates/base.html:81
|
||||
#: changedetectionio/templates/base.html:169
|
||||
msgid "SETTINGS"
|
||||
msgstr "設定"
|
||||
|
||||
#: changedetectionio/templates/base.html:83
|
||||
#: changedetectionio/templates/base.html:84
|
||||
#: changedetectionio/templates/base.html:170
|
||||
msgid "IMPORT"
|
||||
msgstr "進口"
|
||||
msgstr "導入"
|
||||
|
||||
#: changedetectionio/templates/base.html:86
|
||||
#: changedetectionio/templates/base.html:87
|
||||
#: changedetectionio/templates/base.html:171
|
||||
msgid "BACKUPS"
|
||||
msgstr "備份"
|
||||
|
||||
#: changedetectionio/templates/base.html:90
|
||||
#: changedetectionio/templates/base.html:91
|
||||
#: changedetectionio/templates/base.html:173
|
||||
msgid "EDIT"
|
||||
msgstr "編輯"
|
||||
|
||||
#: changedetectionio/templates/base.html:100
|
||||
#: changedetectionio/templates/base.html:101
|
||||
#: changedetectionio/templates/base.html:177
|
||||
msgid "LOG OUT"
|
||||
msgstr "退出"
|
||||
|
||||
#: changedetectionio/templates/base.html:109
|
||||
#: changedetectionio/templates/base.html:108
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "搜索或使用 Alt+S 鍵"
|
||||
|
||||
#: changedetectionio/templates/base.html:116
|
||||
#: changedetectionio/templates/base.html:114
|
||||
msgid "Toggle Light/Dark Mode"
|
||||
msgstr "切換亮/暗模式"
|
||||
|
||||
#: changedetectionio/templates/base.html:117
|
||||
#: changedetectionio/templates/base.html:115
|
||||
msgid "Toggle light/dark mode"
|
||||
msgstr "切換亮/暗模式"
|
||||
|
||||
#: changedetectionio/templates/base.html:127
|
||||
#: changedetectionio/templates/base.html:125
|
||||
msgid "Change Language"
|
||||
msgstr "更改語言"
|
||||
|
||||
#: changedetectionio/templates/base.html:128
|
||||
#: changedetectionio/templates/base.html:126
|
||||
msgid "Change language"
|
||||
msgstr "更改語言"
|
||||
|
||||
#: changedetectionio/templates/base.html:249
|
||||
#: changedetectionio/templates/base.html:253
|
||||
#, fuzzy
|
||||
msgid "Watch List"
|
||||
msgstr "監控列表"
|
||||
|
||||
#: changedetectionio/templates/base.html:258
|
||||
#, fuzzy
|
||||
msgid "Watches"
|
||||
msgstr "監控項"
|
||||
|
||||
#: changedetectionio/templates/base.html:261
|
||||
msgid "Queue Status"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#, fuzzy
|
||||
msgid "Queue"
|
||||
msgstr "排隊"
|
||||
|
||||
#: changedetectionio/templates/base.html:274
|
||||
#: changedetectionio/templates/base.html:279
|
||||
#, fuzzy
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: changedetectionio/templates/base.html:293
|
||||
msgid "Sitemap Crawler"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:318
|
||||
msgid "Sitemap"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:354
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "離線實時更新"
|
||||
|
||||
#: changedetectionio/templates/base.html:259
|
||||
#: changedetectionio/templates/base.html:364
|
||||
msgid "Select Language"
|
||||
msgstr "選擇語言"
|
||||
|
||||
#: changedetectionio/templates/base.html:270
|
||||
#: changedetectionio/templates/base.html:375
|
||||
msgid ""
|
||||
"Language support is in beta, please help us improve by opening a PR on "
|
||||
"GitHub with any updates."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:10
|
||||
#: changedetectionio/templates/base.html:387
|
||||
#: changedetectionio/templates/base.html:400
|
||||
#, fuzzy
|
||||
msgid "Search"
|
||||
msgstr "搜尋中"
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
msgid "URL or Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html:392
|
||||
#, fuzzy
|
||||
msgid "in"
|
||||
msgstr "資訊"
|
||||
|
||||
#: changedetectionio/templates/base.html:393
|
||||
msgid "Enter search term..."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/login.html:11
|
||||
msgid "Password"
|
||||
msgstr "密碼"
|
||||
|
||||
#: changedetectionio/templates/login.html:16
|
||||
#: changedetectionio/templates/login.html:17
|
||||
msgid "Login"
|
||||
msgstr "登入"
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ janus # Thread-safe async/sync queue bridge
|
||||
flask_wtf~=1.2
|
||||
flask~=3.1
|
||||
flask-socketio~=5.6.0
|
||||
python-socketio~=5.14.3
|
||||
python-engineio~=4.12.3
|
||||
python-socketio~=5.16.0
|
||||
python-engineio~=4.13.0
|
||||
inscriptis~=2.2
|
||||
pytz
|
||||
timeago~=1.0
|
||||
@@ -60,7 +60,7 @@ cryptography==46.0.3
|
||||
paho-mqtt!=2.0.*
|
||||
|
||||
# Used for CSS filtering, JSON extraction from HTML
|
||||
beautifulsoup4>=4.0.0,<=4.14.2
|
||||
beautifulsoup4>=4.0.0,<=4.14.3
|
||||
|
||||
# XPath filtering, lxml is required by bs4 anyway, but put it here to be safe.
|
||||
# #2328 - 5.2.0 and 5.2.1 had extra CPU flag CFLAGS set which was not compatible on older hardware
|
||||
@@ -148,7 +148,7 @@ tzdata
|
||||
pluggy ~= 1.6
|
||||
|
||||
# Needed for testing, cross-platform for process and system monitoring
|
||||
psutil==7.1.0
|
||||
psutil==7.2.1
|
||||
|
||||
ruff >= 0.11.2
|
||||
pre_commit >= 4.2.0
|
||||
|
||||
Reference in New Issue
Block a user