UI - Set default favicon, offer option to disable favicons (#3316)

better-merge-of-new-values
dgtlmoon 2025-07-14 18:13:16 +02:00 zatwierdzone przez GitHub
rodzic 438871429c
commit 5980bd9bcd
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
7 zmienionych plików z 82 dodań i 44 usunięć

Wyświetl plik

@ -256,6 +256,11 @@ nav
{{ render_checkbox_field(form.application.form.ui.form.socket_io_enabled, class="socket_io_enabled") }} {{ render_checkbox_field(form.application.form.ui.form.socket_io_enabled, class="socket_io_enabled") }}
<span class="pure-form-message-inline">Realtime UI Updates Enabled - (Restart required if this is changed)</span> <span class="pure-form-message-inline">Realtime UI Updates Enabled - (Restart required if this is changed)</span>
</div> </div>
<div class="pure-control-group">
{{ render_checkbox_field(form.application.form.ui.form.favicons_enabled, class="") }}
<span class="pure-form-message-inline">Enable or Disable Favicons next to the watch list</span>
</div>
</div> </div>
<div class="tab-pane-inner" id="proxies"> <div class="tab-pane-inner" id="proxies">
<div id="recommended-proxy"> <div id="recommended-proxy">

Wyświetl plik

@ -83,8 +83,10 @@ document.addEventListener('DOMContentLoaded', function() {
{%- endif -%} {%- endif -%}
<div id="watch-table-wrapper"> <div id="watch-table-wrapper">
{%- set table_classes = [
<table class="pure-table pure-table-striped watch-table"> 'favicon-enabled' if datastore.data['settings']['application']['ui'].get('favicons_enabled') else 'favicon-not-enabled',
] -%}
<table class="pure-table pure-table-striped watch-table {{ table_classes | reject('equalto', '') | join(' ') }}">
<thead> <thead>
<tr> <tr>
{%- set link_order = "desc" if sort_order == 'asc' else "asc" -%} {%- set link_order = "desc" if sort_order == 'asc' else "asc" -%}
@ -114,7 +116,7 @@ document.addEventListener('DOMContentLoaded', function() {
{%- for watch in (watches|sort(attribute=sort_attribute, reverse=sort_order == 'asc'))|pagination_slice(skip=pagination.skip) -%} {%- for watch in (watches|sort(attribute=sort_attribute, reverse=sort_order == 'asc'))|pagination_slice(skip=pagination.skip) -%}
{%- set checking_now = is_checking_now(watch) -%} {%- set checking_now = is_checking_now(watch) -%}
{%- set history_n = watch.history_n -%} {%- set history_n = watch.history_n -%}
{%- set has_favicon = watch.get_favicon_filename() -%} {%- set favicon = watch.get_favicon_filename() -%}
{# Mirror in changedetectionio/static/js/realtime.js for the frontend #} {# Mirror in changedetectionio/static/js/realtime.js for the frontend #}
{%- set row_classes = [ {%- set row_classes = [
loop.cycle('pure-table-odd', 'pure-table-even'), loop.cycle('pure-table-odd', 'pure-table-even'),
@ -123,7 +125,7 @@ document.addEventListener('DOMContentLoaded', function() {
'paused' if watch.paused is defined and watch.paused != False else '', 'paused' if watch.paused is defined and watch.paused != False else '',
'unviewed' if watch.has_unviewed else '', 'unviewed' if watch.has_unviewed else '',
'has-restock-info' if watch.has_restock_info else 'no-restock-info', 'has-restock-info' if watch.has_restock_info else 'no-restock-info',
'has-favicon' if has_favicon else '', 'has-favicon' if favicon else '',
'in-stock' if watch.has_restock_info and watch['restock']['in_stock'] else '', 'in-stock' if watch.has_restock_info and watch['restock']['in_stock'] else '',
'not-in-stock' if watch.has_restock_info and not watch['restock']['in_stock'] else '', 'not-in-stock' if watch.has_restock_info and not watch['restock']['in_stock'] else '',
'queued' if watch.uuid in queued_uuids else '', 'queued' if watch.uuid in queued_uuids else '',
@ -145,9 +147,11 @@ document.addEventListener('DOMContentLoaded', function() {
<td class="title-col inline"> <td class="title-col inline">
<div class="flex-wrapper"> <div class="flex-wrapper">
{% if datastore.data['settings']['application']['ui'].get('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' #} <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" style="display: none;" class="favicon" loading="lazy" decoding="async" fetchpriority="low" {% if has_favicon %} src="{{url_for('static_content', group='favicon', filename=watch.uuid)}}" {% else %} src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="{% 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> </div>
{% endif %}
<div> <div>
<span class="watch-title"> <span class="watch-title">
{{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}}&nbsp;<a class="external" target="_blank" rel="noopener" href="{{ watch.link.replace('source:','') }}">&nbsp;</a> {{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}}&nbsp;<a class="external" target="_blank" rel="noopener" href="{{ watch.link.replace('source:','') }}">&nbsp;</a>

Wyświetl plik

@ -740,6 +740,7 @@ class globalSettingsRequestForm(Form):
class globalSettingsApplicationUIForm(Form): class globalSettingsApplicationUIForm(Form):
open_diff_in_new_tab = BooleanField("Open 'History' page in a new tab", default=True, validators=[validators.Optional()]) open_diff_in_new_tab = BooleanField("Open 'History' page in a new tab", default=True, validators=[validators.Optional()])
socket_io_enabled = BooleanField('Realtime UI Updates Enabled', default=True, validators=[validators.Optional()]) socket_io_enabled = BooleanField('Realtime UI Updates Enabled', default=True, validators=[validators.Optional()])
favicons_enabled = BooleanField('Favicons Enabled', default=True, validators=[validators.Optional()])
# datastore.data['settings']['application'].. # datastore.data['settings']['application']..
class globalSettingsApplicationForm(commonSettingsForm): class globalSettingsApplicationForm(commonSettingsForm):

Wyświetl plik

@ -63,6 +63,7 @@ class model(dict):
'ui': { 'ui': {
'open_diff_in_new_tab': True, 'open_diff_in_new_tab': True,
'socket_io_enabled': True, 'socket_io_enabled': True,
'favicons_enabled': True
}, },
} }
} }

Wyświetl plik

@ -1,27 +1,42 @@
.watch-table { .watch-table {
&.favicon-not-enabled {
tr {
.favicon {
display: none;
}
}
}
&.favicon-enabled {
tr {
/* make the icons and the text inline-ish */
td.inline.title-col {
.flex-wrapper {
display: flex;
align-items: center;
gap: 4px;
}
}
}
}
td, td,
th { th {
vertical-align: middle; vertical-align: middle;
} }
tr.has-favicon { tr.has-favicon {
img.favicon {
display: inline-block !important;
}
&.unviewed { &.unviewed {
img.favicon { img.favicon {
opacity: 1.0 !important; opacity: 1.0 !important;
} }
} }
} }
.status-icons { .status-icons {
white-space: nowrap; white-space: nowrap;
display: flex; display: flex;
align-items: center; /* Vertical centering */ align-items: center; /* Vertical centering */
gap: 4px; /* Space between image and text */ gap: 4px; /* Space between image and text */
> * { > * {
vertical-align: middle; vertical-align: middle;
} }
@ -55,33 +70,23 @@
padding-right: 4px; padding-right: 4px;
} }
tr.has-favicon { // Reserved for future use
td.inline.title-col { /* &.thumbnail-type-screenshot {
.flex-wrapper { tr.has-favicon {
display: flex; td.inline.title-col {
align-items: center; img.thumbnail {
gap: 4px; background-color: #fff; !* fallback bg for SVGs without bg *!
} border-radius: 4px; !* subtle rounded corners *!
} border: 1px solid #ddd; !* light border for contrast *!
} box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); !* soft shadow *!
filter: contrast(1.05) saturate(1.1) drop-shadow(0 0 0.5px rgba(0, 0, 0, 0.2));
// Reserved for future use object-fit: cover; !* crop/fill if needed *!
/* &.thumbnail-type-screenshot { opacity: 0.8;
tr.has-favicon { max-width: 30px;
td.inline.title-col { max-height: 30px;
img.thumbnail { height: 30px;
background-color: #fff; !* fallback bg for SVGs without bg *! }
border-radius: 4px; !* subtle rounded corners *!
border: 1px solid #ddd; !* light border for contrast *!
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); !* soft shadow *!
filter: contrast(1.05) saturate(1.1) drop-shadow(0 0 0.5px rgba(0, 0, 0, 0.2));
object-fit: cover; !* crop/fill if needed *!
opacity: 0.8;
max-width: 30px;
max-height: 30px;
height: 30px;
} }
} }
} }*/
}*/
} }

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -14,9 +14,12 @@ def test_fetch_webdriver_content(client, live_server, measure_memory_usage):
##################### #####################
res = client.post( res = client.post(
url_for("settings.settings_page"), url_for("settings.settings_page"),
data={"application-empty_pages_are_a_change": "", data={
"requests-time_between_check-minutes": 180, "application-empty_pages_are_a_change": "",
'application-fetch_backend': "html_webdriver"}, "requests-time_between_check-minutes": 180,
'application-fetch_backend': "html_webdriver",
'application-ui-favicons_enabled': "y",
},
follow_redirects=True follow_redirects=True
) )
@ -61,3 +64,22 @@ def test_fetch_webdriver_content(client, live_server, measure_memory_usage):
) )
assert res.status_code == 200 assert res.status_code == 200
assert len(res.data) > 10 assert len(res.data) > 10
##################### disable favicons check
res = client.post(
url_for("settings.settings_page"),
data={
"requests-time_between_check-minutes": 180,
'application-ui-favicons_enabled': "",
"application-empty_pages_are_a_change": "",
},
follow_redirects=True
)
assert b"Settings updated." in res.data
res = client.get(
url_for("watchlist.index"),
)
# The UI can access it here
assert f'src="/static/favicon'.encode('utf8') not in res.data