mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-01-16 12:10:21 +00:00
377 lines
17 KiB
HTML
377 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="{{ get_locale() }}" data-darkmode="{{ get_darkmode_state() }}">
|
|
|
|
<head>
|
|
<meta charset="utf-8" >
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" >
|
|
<meta name="description" content="Self hosted website change detection." >
|
|
<meta name="robots" content="noindex">
|
|
<title>Change Detection{{extra_title}}</title>
|
|
{% if app_rss_token %}
|
|
<link rel="alternate" type="application/rss+xml" title="Changedetection.io » Feed{% if active_tag_uuid %}- {{active_tag.title}}{% endif %}" href="{{ url_for('rss.feed', tag=active_tag_uuid, token=app_rss_token, _external=True )}}" >
|
|
|
|
{% if rss_uuid_feed %}
|
|
<link rel="alternate" type="application/rss+xml" title="Feed » {{ rss_uuid_feed['label'] }}" href="{{ rss_uuid_feed['url'] }}" >
|
|
|
|
{%- endif -%}
|
|
{%- endif -%}
|
|
<link rel="stylesheet" href="{{url_for('static_content', group='styles', filename='pure-min.css')}}" >
|
|
<link rel="stylesheet" href="{{url_for('static_content', group='styles', filename='flag-icons.min.css')}}" >
|
|
<link rel="stylesheet" href="{{url_for('static_content', group='styles', filename='styles.css')}}?v={{ get_css_version() }}" >
|
|
{% if extra_stylesheets %}
|
|
{% for m in extra_stylesheets %}
|
|
<link rel="stylesheet" href="{{ m }}?ver={{ get_css_version() }}" >
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="{{url_for('static_content', group='favicons', filename='apple-touch-icon.png')}}">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="{{url_for('static_content', group='favicons', filename='favicon-32x32.png')}}">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="{{url_for('static_content', group='favicons', filename='favicon-16x16.png')}}">
|
|
<link rel="manifest" href="{{url_for('static_content', group='favicons', filename='site.webmanifest')}}">
|
|
<link rel="mask-icon" href="{{url_for('static_content', group='favicons', filename='safari-pinned-tab.svg')}}" color="#5bbad5">
|
|
<link rel="shortcut icon" href="{{url_for('static_content', group='favicons', filename='favicon.ico')}}">
|
|
<meta name="msapplication-TileColor" content="#da532c">
|
|
<meta name="msapplication-config" content="favicons/browserconfig.xml">
|
|
<meta name="theme-color" content="#ffffff">
|
|
<script>
|
|
const csrftoken="{{ csrf_token() }}";
|
|
const socketio_url="{{ get_socketio_path() }}/socket.io";
|
|
const is_authenticated = {% if current_user.is_authenticated or not has_password %}true{% else %}false{% endif %};
|
|
</script>
|
|
<script src="{{url_for('static_content', group='js', filename='jquery-3.6.0.min.js')}}"></script>
|
|
<script src="{{url_for('static_content', group='js', filename='csrf.js')}}" defer></script>
|
|
<script src="{{url_for('static_content', group='js', filename='feather-icons.min.js')}}" defer></script>
|
|
{% if socket_io_enabled %}
|
|
<script src="{{url_for('static_content', group='js', filename='socket.io.min.js')}}"></script>
|
|
<script src="{{url_for('static_content', group='js', filename='realtime.js')}}" defer></script>
|
|
{% endif %}
|
|
</head>
|
|
|
|
<body class="{{extra_classes}}">
|
|
<div class="header">
|
|
<div {% if pure_menu_fixed != False %}class="pure-menu-fixed"{% endif %} style="width: 100%;">
|
|
<div class="home-menu pure-menu pure-menu-horizontal" id="nav-menu">
|
|
|
|
{% if has_password and not current_user.is_authenticated %}
|
|
<a id="cdio-logo" class="pure-menu-heading" href="https://changedetection.io" rel="noopener">
|
|
<strong>Change</strong>Detection.io</a>
|
|
{% else %}
|
|
<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) %}
|
|
<a class="current-diff-url" href="{{ current_diff_url }}">
|
|
<span style="max-width: 30%; overflow: hidden">{{ current_diff_url }}</span></a>
|
|
{% else %}
|
|
{% if new_version_available and not(has_password and not current_user.is_authenticated) %}
|
|
<span id="new-version-text" class="pure-menu-heading">
|
|
<a href="https://changedetection.io">A new version is available</a>
|
|
</span>
|
|
{% endif %}
|
|
{% 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 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 %}
|
|
{% endif %}
|
|
|
|
<li class="pure-menu-item" id="heart-us">
|
|
<svg
|
|
fill="#ff0000"
|
|
class="bi bi-heart"
|
|
preserveAspectRatio="xMidYMid meet"
|
|
viewBox="0 0 16.9 16.1"
|
|
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" />
|
|
</svg>
|
|
</li>
|
|
<!-- Hamburger menu button (mobile only) -->
|
|
<li class="pure-menu-item">
|
|
<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>
|
|
</div>
|
|
{% endif %}
|
|
{% if left_sticky %}
|
|
<div class="sticky-tab" id="left-sticky">
|
|
</div>
|
|
{% endif %}
|
|
{% if right_sticky %}
|
|
<div class="sticky-tab" id="right-sticky">{{ right_sticky }}</div>
|
|
{% endif %}
|
|
<section class="content">
|
|
<div id="overlay">
|
|
<div class="content">
|
|
<h4>Try our Chrome extension</h4>
|
|
<p>
|
|
<a id="chrome-extension-link"
|
|
title="Chrome Extension - Web Page Change Detection with changedetection.io!"
|
|
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')}}">
|
|
Chrome Webstore
|
|
</a>
|
|
</p>
|
|
Easily add the current web-page from your browser directly into your changedetection.io tool, more great features coming soon!
|
|
<h4>Changedetection.io needs your support!</h4>
|
|
<p>
|
|
You can help us by supporting changedetection.io on these platforms;
|
|
</p>
|
|
<p>
|
|
<ul>
|
|
<li>
|
|
<a href="https://alternativeto.net/software/changedetection-io/about/" title="Web page change detection at alternativeto.net">Rate us at
|
|
AlternativeTo.net</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://github.com/dgtlmoon/changedetection.io" title="Web page change detection on GitHub">Star us on GitHub</a>
|
|
</li>
|
|
<li>
|
|
<a rel="nofollow" href="https://twitter.com/change_det_io" title="Web page change detection on Twitter">Follow us at Twitter/X</a>
|
|
</li>
|
|
<li>
|
|
<a rel="nofollow" href="https://www.g2.com/products/changedetection-io/reviews" title="Web page change detection reviews at G2">G2 Software reviews</a>
|
|
</li>
|
|
<li>
|
|
<a rel="nofollow" href="https://www.linkedin.com/company/changedetection-io" title="Visit web page change detection at LinkedIn">Check us out on LinkedIn</a>
|
|
</li>
|
|
<li>
|
|
And tell your friends and colleagues :)
|
|
</li>
|
|
</ul>
|
|
<p>
|
|
The more popular changedetection.io is, the more time we can dedicate to adding amazing features!
|
|
</p>
|
|
<p>
|
|
Many thanks :)<br>
|
|
</p>
|
|
<p>
|
|
<i>changedetection.io team</i>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content-wrapper">
|
|
{#
|
|
{% if current_user.is_authenticated or not has_password %}
|
|
<aside class="action-sidebar">
|
|
<a href="{{ url_for('watchlist.index') }}" class="action-sidebar-item {% if request.endpoint.startswith('watchlist.') or request.endpoint.startswith('ui.') %}active{% endif %}" title="{{ _('Watch List') }}">
|
|
<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>
|
|
{% if bottom_horizontal_offscreen_contents %}
|
|
<div id="bottom-horizontal-offscreen" style="display:none">
|
|
{{ bottom_horizontal_offscreen_contents|safe }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Language Selection Modal -->
|
|
<dialog id="language-modal" class="modal-dialog" aria-labelledby="language-modal-title">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="language-modal-title">{{ _('Select Language') }}</h2>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="language-list">
|
|
{% for locale, lang_data in available_languages.items()|sort %}
|
|
<a href="{{ url_for('set_language', locale=locale, redirect=request.path) }}" class="language-option" data-locale="{{ locale }}">
|
|
<span class="lang-option {{ lang_data.flag }}"></span> <span class="language-name">{{ lang_data.name }}</span>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
<div>
|
|
{{ _('Language support is in beta, please help us improve by opening a PR on GitHub with any updates.') }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="pure-button" id="close-language-modal">{{ _('Cancel') }}</button>
|
|
</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>
|