diff --git a/changedetectionio/blueprint/watchlist/__init__.py b/changedetectionio/blueprint/watchlist/__init__.py
index 300eeebd..173c50ea 100644
--- a/changedetectionio/blueprint/watchlist/__init__.py
+++ b/changedetectionio/blueprint/watchlist/__init__.py
@@ -84,7 +84,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
has_proxies=datastore.proxy_list,
has_unviewed=datastore.has_unviewed,
hosted_sticky=os.getenv("SALTED_PASS", False) == False,
- now_time_server=time.time(),
+ now_time_server=round(time.time()),
pagination=pagination,
queued_uuids=[q_uuid.item['uuid'] for q_uuid in update_q.queue],
search_q=request.args.get('q', '').strip(),
diff --git a/changedetectionio/blueprint/watchlist/templates/watch-overview.html b/changedetectionio/blueprint/watchlist/templates/watch-overview.html
index 91510221..f552f2ad 100644
--- a/changedetectionio/blueprint/watchlist/templates/watch-overview.html
+++ b/changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -102,7 +102,7 @@
{% set is_unviewed = watch.newest_history_key| int > watch.last_viewed and watch.history_n>=2 %}
{% set checking_now = is_checking_now(watch) %}
-
| {{ loop.index+pagination.skip }} |
@@ -144,7 +143,7 @@
Try other proxies/location
{% endif %}
Try adding external proxies/locations
-
+
{% endif %}
{% if 'empty result or contain only an image' in watch.last_error %}
more help here.
@@ -192,25 +191,40 @@
{% endif %}
{#last_checked becomes fetch-start-time#}
|
- {% if checking_now %}
- Checking now
+
+ Checking now
+
+ {{watch|format_last_checked_time|safe}}
+ |
+
+
+ {% if watch.history_n >=2 and watch.last_changed >0 %}
+ {{watch.last_changed|format_timestamp_timeago}}
{% else %}
- {{watch|format_last_checked_time|safe}}
+ Not yet
{% endif %}
|
-
- {% if watch.history_n >=2 and watch.last_changed >0 %}{{watch|format_last_checked_time|safe}}{% else %}Not yet{% endif %}
- |
- Recheck
- Queued
- Edit
+ {% if watch.uuid in queued_uuids %}Queued{% else %}Recheck{% endif %}
+ Edit
- {% set open_diff_in_new_tab = datastore.data['settings']['application']['ui'].get('open_diff_in_new_tab') %}
- {% set target_attr = ' target="' ~ watch.uuid ~ '"' if open_diff_in_new_tab else '' %}
+ {% if watch.history_n >= 2 %}
- History
- Preview
+ {% set open_diff_in_new_tab = datastore.data['settings']['application']['ui'].get('open_diff_in_new_tab') %}
+ {% set target_attr = ' target="' ~ watch.uuid ~ '"' if open_diff_in_new_tab else '' %}
+
+ {% if is_unviewed %}
+ History
+ {% else %}
+ History
+ {% endif %}
+
+ {% else %}
+ {% if watch.history_n == 1 or (watch.history_n ==0 and watch.error_text_ctime )%}
+ Preview
+ {% endif %}
+ {% endif %}
|
{% endfor %}
@@ -239,4 +253,4 @@
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/changedetectionio/realtime/socket_server.py b/changedetectionio/realtime/socket_server.py
index 71408542..d9802d70 100644
--- a/changedetectionio/realtime/socket_server.py
+++ b/changedetectionio/realtime/socket_server.py
@@ -5,16 +5,16 @@ import json
import time
from loguru import logger
+from changedetectionio.flask_app import _jinja2_filter_datetime
+
+
class ChangeDetectionSocketIO:
def __init__(self, app, datastore):
self.main_app = app
self.datastore = datastore
-
- # Create a separate app for Socket.IO
- self.app = Flask(__name__)
-
+
# Use threading mode instead of eventlet
- self.socketio = SocketIO(self.app,
+ self.socketio = SocketIO(self.main_app,
async_mode='threading',
cors_allowed_origins="*",
logger=False,
@@ -28,23 +28,7 @@ class ChangeDetectionSocketIO:
# Just start a background thread to periodically emit watch status
self.thread = None
self.thread_lock = threading.Lock()
-
- # Set up a simple index route for the Socket.IO app
- @self.app.route('/')
- def index():
- return """
-
-
- ChangeDetection.io Socket.IO Server
-
-
- ChangeDetection.io Socket.IO Server
- This is the Socket.IO server for ChangeDetection.io real-time updates.
- Socket.IO endpoint is available at: /socket.io/
-
-
- """
-
+
def start_background_task(self):
"""Start the background task if it's not already running"""
with self.thread_lock:
@@ -85,22 +69,17 @@ class ChangeDetectionSocketIO:
for thread in threads_snapshot:
if hasattr(thread, 'current_uuid') and thread.current_uuid:
currently_checking.append(thread.current_uuid)
-
+ self.socketio.emit("checking_now", list(currently_checking))
+
# Send all watch data periodically
for uuid, watch in self.datastore.data['watching'].items():
# Simplified watch data to avoid sending everything
simplified_data = {
'uuid': uuid,
- 'url': watch.get('url', ''),
- 'title': watch.get('title', ''),
- 'last_checked': int(watch.get('last_checked', 0)),
- 'last_changed': int(watch.get('newest_history_key', 0)),
- 'history_n': watch.history_n if hasattr(watch, 'history_n') else 0,
- 'unviewed_history': int(watch.get('newest_history_key', 0)) > int(watch.get('last_viewed', 0)) and watch.history_n >=2,
- 'paused': watch.get('paused', False),
- 'checking': uuid in currently_checking
+ 'last_checked': _jinja2_filter_datetime(watch),
+# 'history_n': watch.history_n if hasattr(watch, 'history_n') else 0,
}
- watches_data.append(simplified_data)
+ #watches_data.append(simplified_data)
# Emit all watch data periodically
self.socketio.emit('watch_data', watches_data)
@@ -123,4 +102,4 @@ class ChangeDetectionSocketIO:
# Run the Socket.IO server
# Use 0.0.0.0 to listen on all interfaces
logger.info(f"Starting Socket.IO server on http://{host}:{port}")
- self.socketio.run(self.app, host=host, port=port, debug=False, use_reloader=False, allow_unsafe_werkzeug=True)
\ No newline at end of file
+ self.socketio.run(self.main_app, host=host, port=port, debug=False, use_reloader=False, allow_unsafe_werkzeug=True)
\ No newline at end of file
diff --git a/changedetectionio/static/js/edit-columns-resizer.js b/changedetectionio/static/js/edit-columns-resizer.js
deleted file mode 100644
index 7ca8ceb3..00000000
--- a/changedetectionio/static/js/edit-columns-resizer.js
+++ /dev/null
@@ -1,108 +0,0 @@
-$(document).ready(function() {
- // Global variables for resize functionality
- let isResizing = false;
- let initialX, initialLeftWidth;
-
- // Setup document-wide mouse move and mouse up handlers
- $(document).on('mousemove', handleMouseMove);
- $(document).on('mouseup', function() {
- isResizing = false;
- });
-
- // Handle mouse move for resizing
- function handleMouseMove(e) {
- if (!isResizing) return;
-
- const $container = $('#filters-and-triggers > div');
- const containerWidth = $container.width();
- const diffX = e.clientX - initialX;
- const newLeftWidth = ((initialLeftWidth + diffX) / containerWidth) * 100;
-
- // Limit the minimum width percentage
- if (newLeftWidth > 20 && newLeftWidth < 80) {
- $('#edit-text-filter').css('flex', `0 0 ${newLeftWidth}%`);
- $('#text-preview').css('flex', `0 0 ${100 - newLeftWidth}%`);
- }
- }
-
- // Function to create and setup the resizer
- function setupResizer() {
- // Only proceed if text-preview is visible
- if (!$('#text-preview').is(':visible')) return;
-
- // Don't add another resizer if one already exists
- if ($('#column-resizer').length > 0) return;
-
- // Create resizer element
- const $resizer = $('', {
- class: 'column-resizer',
- id: 'column-resizer'
- });
-
- // Insert before the text preview div
- const $container = $('#filters-and-triggers > div');
- if ($container.length) {
- $resizer.insertBefore('#text-preview');
-
- // Setup mousedown handler for the resizer
- $resizer.on('mousedown', function(e) {
- isResizing = true;
- initialX = e.clientX;
- initialLeftWidth = $('#edit-text-filter').width();
-
- // Prevent text selection during resize
- e.preventDefault();
- });
- }
- }
-
- // Setup resizer when preview is activated
- $('#activate-text-preview').on('click', function() {
- // Give it a small delay to ensure the preview is visible
- setTimeout(setupResizer, 100);
- });
-
- // Also setup resizer when the filters-and-triggers tab is clicked
- $('#filters-and-triggers-tab a').on('click', function() {
- // Give it a small delay to ensure everything is loaded
- setTimeout(setupResizer, 100);
- });
-
- // Run the setupResizer function when the page is fully loaded
- // to ensure it's added if the text-preview is already visible
- setTimeout(setupResizer, 500);
-
- // Handle window resize events
- $(window).on('resize', function() {
- // Make sure the resizer is added if text-preview is visible
- if ($('#text-preview').is(':visible')) {
- setupResizer();
- }
- });
-
- // Keep checking if the resizer needs to be added
- // This ensures it's restored if something removes it
- setInterval(function() {
- if ($('#text-preview').is(':visible')) {
- setupResizer();
- }
- }, 500);
-
- // Add a MutationObserver to watch for DOM changes
- // This will help restore the resizer if it gets removed
- const observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- if (mutation.type === 'childList' &&
- $('#text-preview').is(':visible') &&
- $('#column-resizer').length === 0) {
- setupResizer();
- }
- });
- });
-
- // Start observing the container for DOM changes
- observer.observe(document.getElementById('filters-and-triggers'), {
- childList: true,
- subtree: true
- });
-});
\ No newline at end of file
diff --git a/changedetectionio/static/js/socket.js b/changedetectionio/static/js/socket.js
index 199d1ae6..201a9b7c 100644
--- a/changedetectionio/static/js/socket.js
+++ b/changedetectionio/static/js/socket.js
@@ -14,13 +14,26 @@ $(document).ready(function() {
socket.on('disconnect', function() {
console.log('Socket.IO disconnected');
});
-
+
+ socket.on('checking_now', function(uuid_list) {
+ console.log("Got checking now update");
+ // Remove 'checking-now' class where it should no longer be
+ $('.watch-table tbody tr.checking-now').each(function() {
+ if (!uuid_list.includes($(this).data('watch-uuid'))) {
+ $(this).removeClass('checking-now');
+ }
+ });
+
+ // Add the class on the rows where it should be
+ uuid_list.forEach(function(uuid) {
+ $('.watch-table tbody tr[data-watch-uuid="' + uuid + '"]').addClass('checking-now');
+ });
+ });
+
// Listen for periodically emitted watch data
socket.on('watch_data', function(watches) {
- console.log('Received watch data updates');
-
- // First, remove checking-now class from all rows
- $('.checking-now').removeClass('checking-now');
+/* console.log('Received watch data updates');
+
// Update all watches with their current data
watches.forEach(function(watch) {
@@ -28,12 +41,13 @@ $(document).ready(function() {
if ($watchRow.length) {
updateWatchRow($watchRow, watch);
}
- });
+ });*/
});
// Function to update a watch row with new data
function updateWatchRow($row, data) {
// Update the last-checked time
+ return;
const $lastChecked = $row.find('.last-checked');
if ($lastChecked.length) {
// Update data-timestamp attribute
@@ -63,60 +77,13 @@ $(document).ready(function() {
}
}
}
-
- // Update the last-changed time
- const $lastChanged = $row.find('.last-changed');
- if ($lastChanged.length && data.last_changed) {
- // Update data-timestamp attribute
- $lastChanged.attr('data-timestamp', data.last_changed);
-
- // Only update the text if we have history
- if (data.history_n >= 2 && data.last_changed > 0) {
- let $timeagoSpan = $lastChanged.find('.timeago');
-
- // If there's no timeago span yet, create one
- if (!$timeagoSpan.length) {
- $lastChanged.html('');
- $timeagoSpan = $lastChanged.find('.timeago');
- }
-
- // Format as timeago
- if (typeof timeago !== 'undefined') {
- $timeagoSpan.text(timeago.format(data.last_changed * 1000));
- } else {
- // Simple fallback if timeago isn't available
- const date = new Date(data.last_changed * 1000);
- $timeagoSpan.text(date.toLocaleString());
- }
- } else {
- $lastChanged.text('Not yet');
- }
- }
+
// Toggle the unviewed class based on viewed status
- $row.toggleClass('unviewed', data.unviewed_history === false);
+// $row.toggleClass('unviewed', data.unviewed_history === false);
// If the watch is currently being checked
- $row.toggleClass('checking-now', data.checking === true);
-
- // If a change was detected and not viewed, add highlight effect
- if (data.history_n > 0 && data.viewed === false) {
- // Don't add the highlight effect too often
- if (!$row.hasClass('socket-highlight')) {
- $row.addClass('socket-highlight');
- setTimeout(function() {
- $row.removeClass('socket-highlight');
- }, 2000);
-
- console.log('New change detected for:', data.title || data.url);
- }
-
- // Update any change count indicators if present
- const $changeCount = $row.find('.change-count');
- if ($changeCount.length) {
- $changeCount.text(data.history_n);
- }
- }
+ // $row.toggleClass('checking-now', data.checking === true);
}
} catch (e) {
// If Socket.IO fails to initialize, just log it and continue
diff --git a/changedetectionio/static/js/watch-overview.js b/changedetectionio/static/js/watch-overview.js
index e3bc3359..1d537e43 100644
--- a/changedetectionio/static/js/watch-overview.js
+++ b/changedetectionio/static/js/watch-overview.js
@@ -68,7 +68,7 @@ $(function () {
if (eta_complete + 2 > nowtimeserver && fetch_duration > 3) {
const remaining_seconds = Math.abs(eta_complete) - nowtimeserver - 1;
- let r = (1.0 - (remaining_seconds / fetch_duration)) * 100;
+ let r = Math.round((1.0 - (remaining_seconds / fetch_duration)) * 100);
if (r < 10) {
r = 10;
}
@@ -76,7 +76,6 @@ $(function () {
r = 100;
}
$(this).css('background-size', `${r}% 100%`);
- //$(this).text(`${r}% remain ${remaining_seconds}`);
} else {
$(this).css('background-size', `100% 100%`);
}
diff --git a/changedetectionio/static/styles/scss/parts/_socket.scss b/changedetectionio/static/styles/scss/parts/_socket.scss
index b6b472d8..8eff565d 100644
--- a/changedetectionio/static/styles/scss/parts/_socket.scss
+++ b/changedetectionio/static/styles/scss/parts/_socket.scss
@@ -1,30 +1 @@
// Styles for Socket.IO real-time updates
-
-@keyframes socket-highlight-flash {
- 0% {
- background-color: rgba(var(--color-change-highlight-rgb), 0);
- }
- 50% {
- background-color: rgba(var(--color-change-highlight-rgb), 0.4);
- }
- 100% {
- background-color: rgba(var(--color-change-highlight-rgb), 0);
- }
-}
-
-.socket-highlight {
- animation: socket-highlight-flash 2s ease-in-out;
-}
-
-// Animation for the checking-now state
-@keyframes checking-progress {
- 0% { background-size: 0% 100%; }
- 100% { background-size: 100% 100%; }
-}
-
-tr.checking-now .last-checked {
- background-image: linear-gradient(to right, rgba(0, 120, 255, 0.2) 0%, rgba(0, 120, 255, 0.1) 100%);
- background-size: 100% 100%;
- background-repeat: no-repeat;
- animation: checking-progress 10s linear;
-}
\ No newline at end of file
diff --git a/changedetectionio/static/styles/scss/parts/_watch_table.scss b/changedetectionio/static/styles/scss/parts/_watch_table.scss
index 9ea0d875..8386ae98 100644
--- a/changedetectionio/static/styles/scss/parts/_watch_table.scss
+++ b/changedetectionio/static/styles/scss/parts/_watch_table.scss
@@ -1,3 +1,4 @@
+
/* table related */
.watch-table {
width: 100%;
@@ -7,33 +8,15 @@
&.unviewed {
font-weight: bold;
}
- &.has-history {
- a.preview {
- display: none !important;
- }
- &.history {
- display: inline-block !important;
- }
- }
-
&.error {
color: var(--color-watch-table-error);
}
- &.queued {
- a.queue {
- display: inline-block !important;
- }
- a.recheck {
- display: none !important;
- }
- }
color: var(--color-watch-table-row-text);
}
td {
white-space: nowrap;
-
&.title-col {
word-break: break-all;
white-space: normal;
@@ -64,120 +47,15 @@
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
margin: 0 3px 0 5px;
}
+
+
+ /* Row with 'checking-now' */
+ tr.checking-now {
+ td.last-checked {
+ .spinner, .spinner-text {
+ display: inline-block !important;
+ }
+ }
+ }
}
-
-@media only screen and (max-width: 760px),
-(min-device-width: 768px) and (max-device-width: 800px) {
- /*
-Max width before this PARTICULAR table gets nasty
-This query will take effect for any screen smaller than 760px
-and also iPads specifically.
-*/
- .watch-table {
- /* make headings work on mobile */
- thead {
- display: block;
-
- tr {
- th {
- display: inline-block;
- // Hide the "Last" text for smaller screens
- @media (max-width: 768px) {
- .hide-on-mobile {
- display: none;
- }
- }
- }
- }
-
- .empty-cell {
- display: none;
- }
- }
-
- /* Force table to not be like tables anymore */
- tbody {
- td,
- tr {
- display: block;
- }
- }
-
- tbody {
- tr {
- display: flex;
- flex-wrap: wrap;
-
- // The third child of each row will take up the remaining space
- // This is useful for the URL column, which should expand to fill the remaining space
- :nth-child(3) {
- flex-grow: 1;
- }
-
- // The last three children (from the end) of each row will take up the full width
- // This is useful for the "Last Checked", "Last Changed", and the action buttons columns, which should each take up the full width
- :nth-last-child(-n+3) {
- flex-basis: 100%;
- }
- }
- }
-
- .last-checked {
- > span {
- vertical-align: middle;
- }
- }
-
- .last-checked::before {
- color: var(--color-last-checked);
- content: "Last Checked ";
- }
-
- .last-changed::before {
- color: var(--color-last-checked);
- content: "Last Changed ";
- }
-
- /* Force table to not be like tables anymore */
- td.inline {
- display: inline-block;
- }
-
- .pure-table td,
- .pure-table th {
- border: none;
- }
-
- td {
- /* Behave like a "row" */
- border: none;
- border-bottom: 1px solid var(--color-border-watch-table-cell);
- vertical-align: middle;
-
- &:before {
- /* Top/left values mimic padding */
- top: 6px;
- left: 6px;
- width: 45%;
- padding-right: 10px;
- white-space: nowrap;
- }
- }
-
- &.pure-table-striped {
- tr {
- background-color: var(--color-table-background);
- }
-
- tr:nth-child(2n-1) {
- background-color: var(--color-table-stripe);
- }
-
- tr:nth-child(2n-1) td {
- background-color: inherit;
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/changedetectionio/static/styles/scss/styles.scss b/changedetectionio/static/styles/scss/styles.scss
index 21075079..71d44039 100644
--- a/changedetectionio/static/styles/scss/styles.scss
+++ b/changedetectionio/static/styles/scss/styles.scss
@@ -13,6 +13,7 @@
@import "parts/_menu";
@import "parts/_love";
@import "parts/preview_text_filter";
+@import "parts/_watch_table";
@import "parts/_edit";
@import "parts/_conditions_table";
@@ -169,56 +170,6 @@ code {
color: var(--color-text);
}
-/* table related */
-.watch-table {
- width: 100%;
- font-size: 80%;
-
- tr {
- &.unviewed {
- font-weight: bold;
- }
- &.error {
- color: var(--color-watch-table-error);
- }
- color: var(--color-watch-table-row-text);
- }
-
-
- td {
- white-space: nowrap;
- &.title-col {
- word-break: break-all;
- white-space: normal;
- }
- }
-
-
- th {
- white-space: nowrap;
-
- a {
- font-weight: normal;
-
- &.active {
- font-weight: bolder;
- }
-
- &.inactive {
- .arrow {
- display: none;
- }
- }
- }
- }
-
- .title-col a[target="_blank"]::after,
- .current-diff-url::after {
- content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
- margin: 0 3px 0 5px;
- }
-}
-
.inline-tag {
white-space: nowrap;
border-radius: 5px;
diff --git a/changedetectionio/static/styles/styles.css b/changedetectionio/static/styles/styles.css
index 28cb0ec2..14dbd72e 100644
--- a/changedetectionio/static/styles/styles.css
+++ b/changedetectionio/static/styles/styles.css
@@ -523,6 +523,37 @@ body.preview-text-enabled {
z-index: 3;
box-shadow: 1px 1px 4px var(--color-shadow-jump); }
+/* table related */
+.watch-table {
+ width: 100%;
+ font-size: 80%;
+ /* Row with 'checking-now' */ }
+ .watch-table tr {
+ color: var(--color-watch-table-row-text); }
+ .watch-table tr.unviewed {
+ font-weight: bold; }
+ .watch-table tr.error {
+ color: var(--color-watch-table-error); }
+ .watch-table td {
+ white-space: nowrap; }
+ .watch-table td.title-col {
+ word-break: break-all;
+ white-space: normal; }
+ .watch-table th {
+ white-space: nowrap; }
+ .watch-table th a {
+ font-weight: normal; }
+ .watch-table th a.active {
+ font-weight: bolder; }
+ .watch-table th a.inactive .arrow {
+ display: none; }
+ .watch-table .title-col a[target="_blank"]::after,
+ .watch-table .current-diff-url::after {
+ content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
+ margin: 0 3px 0 5px; }
+ .watch-table tr.checking-now td.last-checked .spinner, .watch-table tr.checking-now td.last-checked .spinner-text {
+ display: inline-block !important; }
+
ul#conditions_match_logic {
list-style: none; }
ul#conditions_match_logic input, ul#conditions_match_logic label, ul#conditions_match_logic li {
@@ -735,34 +766,6 @@ code {
background: var(--color-background-code);
color: var(--color-text); }
-/* table related */
-.watch-table {
- width: 100%;
- font-size: 80%; }
- .watch-table tr {
- color: var(--color-watch-table-row-text); }
- .watch-table tr.unviewed {
- font-weight: bold; }
- .watch-table tr.error {
- color: var(--color-watch-table-error); }
- .watch-table td {
- white-space: nowrap; }
- .watch-table td.title-col {
- word-break: break-all;
- white-space: normal; }
- .watch-table th {
- white-space: nowrap; }
- .watch-table th a {
- font-weight: normal; }
- .watch-table th a.active {
- font-weight: bolder; }
- .watch-table th a.inactive .arrow {
- display: none; }
- .watch-table .title-col a[target="_blank"]::after,
- .watch-table .current-diff-url::after {
- content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
- margin: 0 3px 0 5px; }
-
.inline-tag, .watch-tag-list, .tracking-ldjson-price-data, .restock-label {
white-space: nowrap;
border-radius: 5px;