diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 5a407e1f..0331a866 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -283,6 +283,8 @@ def changedetection_app(config=None, datastore_o=None): @app.route("/", methods=['GET']) @login_required def index(): + import uuid + limit_tag = request.args.get('tag') pause_uuid = request.args.get('pause') @@ -292,13 +294,18 @@ def changedetection_app(config=None, datastore_o=None): if pause_uuid: try: + validate = uuid.UUID(str(pause_uuid)) datastore.data['watching'][pause_uuid]['paused'] ^= True - datastore.needs_write = True + except ValueError: + + action = True if pause_uuid == 'pause-all' else False - return redirect(url_for('index', tag = limit_tag)) - except KeyError: - pass + for watch_uuid, watch in datastore.data['watching'].items(): + if datastore.data['watching'][watch_uuid]['tag'] == limit_tag or limit_tag is None : + datastore.data['watching'][watch_uuid]['paused'] = action + datastore.needs_write = True + return redirect(url_for('index', tag = limit_tag)) # Sort by last_changed and add the uuid which is usually the key.. sorted_watches = [] for uuid, watch in datastore.data['watching'].items(): @@ -630,13 +637,139 @@ def changedetection_app(config=None, datastore_o=None): @login_required def mark_all_viewed(): + limit_tag = request.args.get('tag') + # Save the current newest history as the most recently viewed - for watch_uuid, watch in datastore.data['watching'].items(): - datastore.set_last_viewed(watch_uuid, watch['newest_history_key']) + try: + for watch_uuid, watch in datastore.data['watching'].items(): + if datastore.data['watching'][watch_uuid]['tag'] == limit_tag or limit_tag is None : + datastore.set_last_viewed(watch_uuid, watch['newest_history_key']) + + datastore.needs_write = True - flash("Cleared all statuses.") - return redirect(url_for('index')) + return redirect(url_for('index', tag = limit_tag)) + except KeyError: + pass + # process selected + @app.route("/api/process-selected", methods=['GET', "POST"]) + @login_required + def process_selected(): + + if request.method == 'POST' : + func = request.form.get('func') + limit_tag = request.form.get('tag') + uuids = request.form.get('uuids') + + if request.method == 'GET' : + func = request.args.get('func') + limit_tag = request.args.get('tag') + uuids = request.args.get('uuids') + + if uuids == '' : + flash("No watches selected.") + return render_template('index') + + else : + + if func == 'recheck_selected' : + + i = 0 + + running_uuids = [] + for t in running_update_threads: + running_uuids.append(t.current_uuid) + + try : + for uuid in uuids.split(',') : + if uuid not in running_uuids and not datastore.data['watching'][uuid]['paused']: + update_q.put(uuid) + i += 1 + + except KeyError : + pass + + flash("Rechecking {0} watch{1}.".format(i, "" if i == 1 else "es")) + + # Clear selected statuses, so we do not see the 'unviewed' class + elif func == 'mark_selected_viewed' : + + try : + for uuid in uuids.split(',') : + datastore.data['watching'][uuid]['last_viewed'] = datastore.data['watching'][uuid]['newest_history_key'] + + except KeyError : + pass + + datastore.needs_write = True + + # Reset selected statuses, so we see the 'unviewed' class + # both funcs will contain the uuid list from the processChecked javascript function + elif func == 'mark_selected_notviewed' or func == 'mark_all_notviewed' : + + # count within limit_tag and count successes and capture unchanged + tagged = 0 + marked = 0 + unchanged = [] + + try : + for uuid in uuids.split(',') : + # increment count with limit_tag + tagged += 1 + dates = list(datastore.data['watching'][uuid]['history'].keys()) + # Convert to int, sort and back to str again + dates = [int(i) for i in dates] + dates.sort(reverse=True) + dates = [str(i) for i in dates] + + # must be more than 1 history to mark as not viewed + if len(dates) > 1 : + # Save the next earliest history as the most recently viewed + datastore.set_last_viewed(uuid, dates[1]) + # increment successes + marked += 1 + + else : + if datastore.data['watching'][uuid]['title'] : + unchanged.append(datastore.data['watching'][uuid]['title']) + else : + unchanged.append(datastore.data['watching'][uuid]['url']) + + except KeyError : + pass + + datastore.needs_write = True + + if marked < tagged : + flash("The following {} not have enough history to be remarked:".format("watch does" if len(unchanged) == 1 else "watches do"), "notice") + for i in range(len(unchanged)): + flash(unchanged[i], "notice") + + elif func == 'delete_selected' : + + # reachable only after confirmation in javascript processChecked(func, tag) function + try : + i = 0 + for uuid in uuids.split(',') : + datastore.delete(uuid) + i += 1 + + except KeyError : + pass + + datastore.needs_write = True + + flash("{0} {1} deleted.".format(i, "watch was" if (i) == 1 else "watches were")) + + else : + + flash("Invalid parameter received.") + + if limit_tag == None or limit_tag == 'None' : + return redirect(url_for('index')) + else : + return redirect(url_for('index', tag = limit_tag)) + @app.route("/diff/", methods=['GET']) @login_required def diff_history_page(uuid): @@ -797,6 +930,10 @@ def changedetection_app(config=None, datastore_o=None): if form.validate(): + # get action parameter (add paused button value is 'add', watch button value is 'watch' + #action = request.form.get('action') + add_paused = request.form.get('add-paused') + url = request.form.get('url').strip() if datastore.url_exists(url): flash('The URL {} already exists'.format(url), "error") @@ -804,10 +941,17 @@ def changedetection_app(config=None, datastore_o=None): # @todo add_watch should throw a custom Exception for validation etc new_uuid = datastore.add_watch(url=url, tag=request.form.get('tag').strip()) - # Straight into the queue. - update_q.put(new_uuid) + + if add_paused : + datastore.data['watching'][new_uuid]['paused'] = True + datastore.needs_write = True + flash("Watch added in a paused state.") + + else : # watch now + # Straight into the queue. + update_q.put(new_uuid) - flash("Watch added.") + flash("Watch added.") return redirect(url_for('index')) else: flash("Error") diff --git a/changedetectionio/static/images/notviewed.svg b/changedetectionio/static/images/notviewed.svg new file mode 100644 index 00000000..15edb65d --- /dev/null +++ b/changedetectionio/static/images/notviewed.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/changedetectionio/static/images/pause.svg b/changedetectionio/static/images/pause.svg index f19206c5..49a2d35d 100644 --- a/changedetectionio/static/images/pause.svg +++ b/changedetectionio/static/images/pause.svg @@ -1,84 +1,44 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/changedetectionio/static/images/play.svg b/changedetectionio/static/images/play.svg new file mode 100644 index 00000000..f040b184 --- /dev/null +++ b/changedetectionio/static/images/play.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/changedetectionio/static/images/search.png b/changedetectionio/static/images/search.png new file mode 100644 index 00000000..69755f00 Binary files /dev/null and b/changedetectionio/static/images/search.png differ diff --git a/changedetectionio/static/images/sortable.svg b/changedetectionio/static/images/sortable.svg new file mode 100644 index 00000000..469938bd --- /dev/null +++ b/changedetectionio/static/images/sortable.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/changedetectionio/static/images/viewed.svg b/changedetectionio/static/images/viewed.svg new file mode 100644 index 00000000..804ad9e1 --- /dev/null +++ b/changedetectionio/static/images/viewed.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/changedetectionio/static/js/tbltools.js b/changedetectionio/static/js/tbltools.js new file mode 100644 index 00000000..2c58f1c2 --- /dev/null +++ b/changedetectionio/static/js/tbltools.js @@ -0,0 +1,338 @@ +// table tools + +// must be a var for keyChar and keyCode use +var CONSTANT_ESCAPE_KEY = 27; + +// global sorting vars (new window is always last_changed, descending) +var loading; +var sort_column; +var sort_order; + +// restore scroll position on submit/reload +document.addEventListener("DOMContentLoaded", function(event) { + var scrollpos = sessionStorage.getItem('scrollpos'); + if (scrollpos) window.scrollTo(0, scrollpos); +}); +// mobile scroll position retention +if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + document.addEventListener("visibilitychange", function() { + storeScrollAndSearch(); + }); +} +else { + // non-mobile scroll position retention + window.onbeforeunload = function(e) { + storeScrollAndSearch(); + }; +} +function storeScrollAndSearch() { + sessionStorage.setItem('scrollpos', window.pageYOffset); + sessionStorage.setItem('searchtxt', document.getElementById("txtInput").value); +} + +// page load functions +function load_functions() { + // loading + loading = true; + // retain checked items + checkChange(); + // retrieve saved sorting + getSort(); + // sort + sortTable(sort_column); + // search + if (isSessionStorageSupported()) { + // retrieve search + if ( sessionStorage.getItem("searchtxt") != null ) { + document.getElementById("txtInput").value = sessionStorage.getItem("searchtxt"); + tblSearch(this); + } + } +} + +// sorting +function sortTable(n) { + var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0; + table = document.getElementById("watch-table"); + switching = true; + //Set the sorting direction, either default 9, 1 or saved + if (loading) { + getSort(); + n = sort_column; + dir = (sort_order == 0) ? "asc" : "desc"; + loading = false; + } + else { + dir = "asc"; + } + /*Make a loop that will continue until + no switching has been done:*/ + while (switching) { + //start by saying: no switching is done: + switching = false; + rows = table.rows; + /*Loop through all table rows (except the + first, which contains table headers):*/ + for (i = 1; i < (rows.length - 1); i++) { + //start by saying there should be no switching: + shouldSwitch = false; + /*Get the two elements you want to compare, + one from current row and one from the next:*/ + x = rows[i].getElementsByTagName("TD")[n]; + y = rows[i + 1].getElementsByTagName("TD")[n]; + x = x.innerHTML.toLowerCase(); + y = y.innerHTML.toLowerCase(); + /* handle # columns */ + if (!isNaN(x)) { + x = parseFloat(x); + y = parseFloat(y); + } + /*check if the two rows should switch place, + based on the direction, asc or desc:*/ + if (dir == "asc") { + if (x > y) { + //if so, mark as a switch and break the loop: + shouldSwitch= true; + break; + } + } else if (dir == "desc") { + if (x < y) { + //if so, mark as a switch and break the loop: + shouldSwitch = true; + break; + } + } + } + if (shouldSwitch) { + /*If a switch has been marked, make the switch + and mark that a switch has been done:*/ + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + //Each time a switch is done, increase this count by 1: + switchcount ++; + } else { + /*If no switching has been done AND the direction is "asc", + set the direction to "desc" and run the while loop again.*/ + if (switchcount == 0 && dir == "asc") { + dir = "desc"; + switching = true; + } + } + } + // hide all asc/desc sort arrows + sortimgs = document.querySelectorAll('[id^="sort-"]'); + for (var i = 0; i < sortimgs.length; i++) { + sortimgs[i].style.display = "none"; + } + // show current asc/desc sort arrow and set sort_order var + if (dir == "asc") { + document.getElementById("sort-" + n + "a").style.display = ""; + } + else { + document.getElementById("sort-" + n + "d").style.display = ""; + } + // show all sortable indicators + sortableimgs = document.querySelectorAll('[id^="sortable-"]'); + for (var i = 0; i < sortableimgs.length; i++) { + sortableimgs[i].style.display = ""; + } + // hide sortable indicator from current column + document.getElementById("sortable-" + n).style.display = "none"; + // save sorting + sessionStorage.setItem("sort_column", n); + sessionStorage.setItem("sort_order", (dir == "asc") ? 0 : 1); + // restripe + restripe(); +} + +// check/uncheck all checkboxes +function checkAll(e) { + var checkboxes = document.getElementsByName('check'); + var checkboxFunctions = document.querySelectorAll('[id=checkbox-functions]'); + if (e.checked) { + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].checked = true; + } + for (var i = 0; i < checkboxFunctions.length; i++) { + checkboxFunctions[i].style.display = ""; + } + } + else { + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].checked = false; + } + for (var i = 0; i < checkboxFunctions.length; i++) { + checkboxFunctions[i].style.display = "none"; + } + } +} + +// check/uncheck checkall checkbox if all other checkboxes are checked/unchecked +function checkChange(){ + var totalCheckbox = document.querySelectorAll('input[name="check"]').length; + var totalChecked = document.querySelectorAll('input[name="check"]:checked').length; + var checkboxFunctions = document.querySelectorAll('[id=checkbox-functions]'); + if(totalCheckbox == totalChecked) { + document.getElementsByName("showhide")[0].checked=true; + } + else { + document.getElementsByName("showhide")[0].checked=false; + } + if(totalChecked == 0) { + for (var i = 0; i < checkboxFunctions.length; i++) { + checkboxFunctions[i].style.display = "none"; + } + } + else { + for (var i = 0; i < checkboxFunctions.length; i++) { + checkboxFunctions[i].style.display = ""; + } + } +} + +// search watches in Title column +function tblSearch(evt) { + var code = evt.charCode || evt.keyCode; + if (code == CONSTANT_ESCAPE_KEY) { + document.getElementById("txtInput").value = ''; + } + var input, filter, table, tr, td, i, txtValue; + input = document.getElementById("txtInput"); + filter = input.value.toUpperCase(); + table = document.getElementById("watch-table"); + tr = table.getElementsByTagName("tr"); + for (i = 0; i < tr.length; i++) { + td = tr[i].getElementsByTagName("td")[5]; // col 5 is the hidden title/url column + if (td) { + txtValue = td.textContent || td.innerText; + if (txtValue.toUpperCase().indexOf(filter) > -1) { + tr[i].style.display = ""; + } + else { + tr[i].style.display = "none"; + } + } + } + restripe(); +} + +// restripe after searching +function restripe () { + var visrows = []; + var table = document.getElementById("watch-table"); + var rows = table.getElementsByTagName("tr"); + + for (i = 0; i < rows.length; i++) { + if (rows[i].style.display !== "none") { + visrows.push(rows[i]); + } + } + for (var i=0 ; i 0 ) { + let output = []; + for (var i = 0; i < checkedArr.length; i++ ) { + output.push( checkedArr[i].parentNode.parentNode.getAttribute("id") ); + } + var uuids = ""; + for (var i = 0; i < checkedArr.length; i++ ) { + if (i < checkedArr.length - 1 ) { + uuids += output[i] + ","; + } else { + uuids += output[i]; + } + } + } else { + uuids = ''; + } + return uuids; +} + +// process selected watches +function processChecked(func, tag) { + if ( func == 'mark_all_notviewed' ) { + uuids = getChecked('all'); + } + else { + uuids = getChecked(); + } + // confirm if deleting + if ( func == 'delete_selected' && uuids.length > 0 ) { + result = confirm('Deletions cannot be undone.\n\nAre you sure you want to continue?'); + if ( result == false) { + return; + } + } + // href locations + var currenturl = window.location; + var posturl = location.protocol + '//' + location.host + '/api/process-selected'; + // posting vars + const XHR = new XMLHttpRequest(), + FD = new FormData(); + // fill form data + FD.append('func', func); + FD.append('tag', tag); + FD.append('uuids', uuids); + // success + XHR.addEventListener( 'load', function( event ) { + window.location = currenturl; + }); + // error + XHR.addEventListener(' error', function( event ) { + alert( 'Error posting request.' ); + }); + // set up request + XHR.open( 'POST', posturl ); + // send + XHR.send( FD ); +} + +function clearSearch() { + document.getElementById("txtInput").value = ''; + tblSearch(CONSTANT_ESCAPE_KEY); +} + +function isSessionStorageSupported() { + var storage = window.sessionStorage; + try { + storage.setItem('test', 'test'); + storage.removeItem('test'); + return true; + } catch (e) { + return false; + } +} + +function getSort() { + if (isSessionStorageSupported()) { + // retrieve sort settings if set + if ( sessionStorage.getItem("sort_column") != null ) { + sort_column = sessionStorage.getItem("sort_column"); + sort_order = sessionStorage.getItem("sort_order"); + } + else { + sort_column = 9; // last changed + sort_order = 1; // desc + //alert("Your web browser does not support retaining sorting and page position."); + } + } +} diff --git a/changedetectionio/static/styles/styles.css b/changedetectionio/static/styles/styles.css index 3461475c..a0be9cf3 100644 --- a/changedetectionio/static/styles/styles.css +++ b/changedetectionio/static/styles/styles.css @@ -1 +1,836 @@ -body{color:#333;background:#262626}.pure-table-even{background:#fff}a{text-decoration:none;color:#1b98f8}a.github-link{color:#fff}.pure-menu-horizontal{background:#fff;padding:5px;display:flex;justify-content:space-between;border-bottom:2px solid #ed5900;align-items:center}section.content{padding-top:5em;padding-bottom:5em;flex-direction:column;display:flex;align-items:center;justify-content:center}.watch-table{width:100%}.watch-table tr.unviewed{font-weight:bold}.watch-table .error{color:#a00}.watch-table td{font-size:80%;white-space:nowrap}.watch-table td.title-col{word-break:break-all;white-space:normal}.watch-table th{white-space:nowrap}.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-tag-list{color:#e70069;white-space:nowrap}.box{max-width:80%;flex-direction:column;display:flex;justify-content:center}#post-list-buttons{text-align:right;padding:0;margin:0}#post-list-buttons li{display:inline-block}#post-list-buttons a{border-top-left-radius:initial;border-top-right-radius:initial;border-bottom-left-radius:5px;border-bottom-right-radius:5px}body:after{content:"";background:linear-gradient(130deg, #ff7a18, #af002d 41.07%, #319197 76.05%)}body:after,body:before{display:block;height:600px;position:absolute;top:0;left:0;width:100%;z-index:-1}body::after{opacity:.91}body::before{content:"";background-image:url(../images/gradient-border.png)}body:before{background-size:cover}body:after,body:before{-webkit-clip-path:polygon(100% 0, 0 0, 0 77.5%, 1% 77.4%, 2% 77.1%, 3% 76.6%, 4% 75.9%, 5% 75.05%, 6% 74.05%, 7% 72.95%, 8% 71.75%, 9% 70.55%, 10% 69.3%, 11% 68.05%, 12% 66.9%, 13% 65.8%, 14% 64.8%, 15% 64%, 16% 63.35%, 17% 62.85%, 18% 62.6%, 19% 62.5%, 20% 62.65%, 21% 63%, 22% 63.5%, 23% 64.2%, 24% 65.1%, 25% 66.1%, 26% 67.2%, 27% 68.4%, 28% 69.65%, 29% 70.9%, 30% 72.15%, 31% 73.3%, 32% 74.35%, 33% 75.3%, 34% 76.1%, 35% 76.75%, 36% 77.2%, 37% 77.45%, 38% 77.5%, 39% 77.3%, 40% 76.95%, 41% 76.4%, 42% 75.65%, 43% 74.75%, 44% 73.75%, 45% 72.6%, 46% 71.4%, 47% 70.15%, 48% 68.9%, 49% 67.7%, 50% 66.55%, 51% 65.5%, 52% 64.55%, 53% 63.75%, 54% 63.15%, 55% 62.75%, 56% 62.55%, 57% 62.5%, 58% 62.7%, 59% 63.1%, 60% 63.7%, 61% 64.45%, 62% 65.4%, 63% 66.45%, 64% 67.6%, 65% 68.8%, 66% 70.05%, 67% 71.3%, 68% 72.5%, 69% 73.6%, 70% 74.65%, 71% 75.55%, 72% 76.35%, 73% 76.9%, 74% 77.3%, 75% 77.5%, 76% 77.45%, 77% 77.25%, 78% 76.8%, 79% 76.2%, 80% 75.4%, 81% 74.45%, 82% 73.4%, 83% 72.25%, 84% 71.05%, 85% 69.8%, 86% 68.55%, 87% 67.35%, 88% 66.2%, 89% 65.2%, 90% 64.3%, 91% 63.55%, 92% 63%, 93% 62.65%, 94% 62.5%, 95% 62.55%, 96% 62.8%, 97% 63.3%, 98% 63.9%, 99% 64.75%, 100% 65.7%);clip-path:polygon(100% 0, 0 0, 0 77.5%, 1% 77.4%, 2% 77.1%, 3% 76.6%, 4% 75.9%, 5% 75.05%, 6% 74.05%, 7% 72.95%, 8% 71.75%, 9% 70.55%, 10% 69.3%, 11% 68.05%, 12% 66.9%, 13% 65.8%, 14% 64.8%, 15% 64%, 16% 63.35%, 17% 62.85%, 18% 62.6%, 19% 62.5%, 20% 62.65%, 21% 63%, 22% 63.5%, 23% 64.2%, 24% 65.1%, 25% 66.1%, 26% 67.2%, 27% 68.4%, 28% 69.65%, 29% 70.9%, 30% 72.15%, 31% 73.3%, 32% 74.35%, 33% 75.3%, 34% 76.1%, 35% 76.75%, 36% 77.2%, 37% 77.45%, 38% 77.5%, 39% 77.3%, 40% 76.95%, 41% 76.4%, 42% 75.65%, 43% 74.75%, 44% 73.75%, 45% 72.6%, 46% 71.4%, 47% 70.15%, 48% 68.9%, 49% 67.7%, 50% 66.55%, 51% 65.5%, 52% 64.55%, 53% 63.75%, 54% 63.15%, 55% 62.75%, 56% 62.55%, 57% 62.5%, 58% 62.7%, 59% 63.1%, 60% 63.7%, 61% 64.45%, 62% 65.4%, 63% 66.45%, 64% 67.6%, 65% 68.8%, 66% 70.05%, 67% 71.3%, 68% 72.5%, 69% 73.6%, 70% 74.65%, 71% 75.55%, 72% 76.35%, 73% 76.9%, 74% 77.3%, 75% 77.5%, 76% 77.45%, 77% 77.25%, 78% 76.8%, 79% 76.2%, 80% 75.4%, 81% 74.45%, 82% 73.4%, 83% 72.25%, 84% 71.05%, 85% 69.8%, 86% 68.55%, 87% 67.35%, 88% 66.2%, 89% 65.2%, 90% 64.3%, 91% 63.55%, 92% 63%, 93% 62.65%, 94% 62.5%, 95% 62.55%, 96% 62.8%, 97% 63.3%, 98% 63.9%, 99% 64.75%, 100% 65.7%)}.arrow{border:solid #000;border-width:0 3px 3px 0;display:inline-block;padding:3px}.arrow.right{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}.arrow.left{transform:rotate(135deg);-webkit-transform:rotate(135deg)}.arrow.up{transform:rotate(-135deg);-webkit-transform:rotate(-135deg)}.arrow.down{transform:rotate(45deg);-webkit-transform:rotate(45deg)}.button-small{font-size:85%}.fetch-error{padding-top:1em;font-size:60%;max-width:400px;display:block}.button-secondary{color:#fff;border-radius:4px;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.button-success{background:#1cb841}.button-tag{background:#636363;color:#fff;font-size:65%;border-bottom-left-radius:initial;border-bottom-right-radius:initial}.button-tag.active{background:#9c9c9c;font-weight:bold}.button-error{background:#ca3c3c}.button-warning{background:#df7514}.button-secondary{background:#42b8dd}.button-cancel{background:#c8c8c8}.messages li{list-style:none;padding:1em;border-radius:10px;color:#fff;font-weight:bold}.messages li.message{background:rgba(255,255,255,0.2)}.messages li.error{background:rgba(255,1,1,0.5)}.messages li.notice{background:rgba(255,255,255,0.5)}#notification-customisation{border:1px solid #ccc;padding:1rem;border-radius:5px}#token-table.pure-table td,#token-table.pure-table th{font-size:80%}#new-watch-form{background:rgba(0,0,0,0.05);padding:1em;border-radius:10px;margin-bottom:1em}#new-watch-form input{width:auto !important;display:inline-block}#new-watch-form .label{display:none}#new-watch-form legend{color:#fff}#diff-col{padding-left:40px}#diff-jump{position:fixed;left:0;top:120px;background:#fff;padding:10px;border-top-right-radius:5px;border-bottom-right-radius:5px;box-shadow:5px 0 5px -2px #888}#diff-jump a{color:#1b98f8;cursor:grabbing;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;-o-user-select:none}footer{padding:10px;background:#fff;color:#444;text-align:center}#feed-icon{vertical-align:middle}.sticky-tab{position:absolute;top:80px;font-size:11px;background:#fff;padding:10px}.sticky-tab#left-sticky{left:0}.sticky-tab#right-sticky{right:0}#new-version-text a{color:#e07171}.paused-state.state-False img{opacity:.2}.paused-state.state-False:hover img{opacity:.8}.monospaced-textarea textarea{width:100%;font-family:monospace;white-space:pre;overflow-wrap:normal;overflow-x:scroll}.pure-form .pure-control-group,.pure-form .pure-group,.pure-form .pure-controls{padding-bottom:1em}.pure-form .pure-control-group div,.pure-form .pure-group div,.pure-form .pure-controls div{margin:0}.pure-form .error input{background-color:#ffebeb}.pure-form ul.errors{padding:.5em .6em;border:1px solid #d00;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form ul.errors li{margin-left:1em;color:#d00}.pure-form label{font-weight:bold}.pure-form textarea{width:100%}.pure-form ul#fetch_backend{margin:0;list-style:none}.pure-form ul#fetch_backend > li > *{display:inline-block}@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px){.box{max-width:95%}.edit-form{padding:.5em;margin:0}#nav-menu{overflow-x:scroll}}@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px){input[type='text']{width:100%}.watch-table thead,.watch-table tbody,.watch-table th,.watch-table td,.watch-table tr{display:block}.watch-table .last-checked::before{color:#555;content:"Last Checked "}.watch-table .last-changed::before{color:#555;content:"Last Changed "}.watch-table td.inline{display:inline-block}.watch-table thead tr{position:absolute;top:-9999px;left:-9999px}.watch-table .pure-table td,.watch-table .pure-table th{border:none}.watch-table td{border:none;border-bottom:1px solid #eee}.watch-table td:before{top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap}.watch-table.pure-table-striped tr{background-color:#fff}.watch-table.pure-table-striped tr:nth-child(2n-1){background-color:#eee}.watch-table.pure-table-striped tr:nth-child(2n-1) td{background-color:inherit}}@media only screen and (min-width: 761px){.m-d{min-width:80%}}.tabs ul{margin:0;padding:0;display:block}.tabs ul li{margin-right:3px;display:inline-block;color:#fff;border-top-left-radius:5px;border-top-right-radius:5px;background-color:rgba(255,255,255,0.2)}.tabs ul li.active,.tabs ul li :target{background-color:#fff}.tabs ul li.active a,.tabs ul li :target a{color:#222;font-weight:bold}.tabs ul li a{display:block;padding:.8em;color:#fff}.pure-form-stacked > div:first-child{display:block}.edit-form{min-width:70%}.edit-form .tab-pane-inner{padding:0}.edit-form .tab-pane-inner:not(:target){display:none}.edit-form .tab-pane-inner:target{display:block}.edit-form .box-wrap{position:relative}.edit-form .inner{background:#fff;padding:20px}.edit-form #actions{display:block;background:#fff} \ No newline at end of file +body { + color: #333; + background: #262626; +} +.pure-table-even { + background: #fff; +} +a { + text-decoration: none; + color: #1b98f8; +} +a.github-link { + color: #fff; +} +.pure-menu-horizontal { + background: #fff; + padding: 5px; + display: flex; + justify-content: space-between; + border-bottom: 2px solid #ed5900; + align-items: center; +} +section.content { + padding-top: 5em; + padding-bottom: 5em; + flex-direction: column; + display: flex; + align-items: center; + justify-content: center; +} +.search-box input { + width: 32px; + border: none; + background-image: url(/static/images/search.png); + background-position: 10px 10px; + background-repeat: no-repeat; + padding: 16px 0px 16px 16px; + -webkit-transition: left 0.4s ease-in-out; + transition: left 0.4s ease-in-out; + color: transparent; +} +.search-box input:hover { + cursor: pointer; +} +.search-box input:focus { + width: 150px; + padding-left: 48px; + cursor: text; + color: #333; +} +.search-box input:(not:focus) { + cursor: default; +} +.search-box input::placeholder-shown { + cursor: text; +} +.search-box input:not(:placeholder-shown) { + width: 150px; + padding-left: 48px; + color: #333; +} +.search-box input::placeholder { + opacity: 0; +} +.search-box input:focus::placeholder { + opacity: 1; +} +.search-box input:not(:-ms-input-placeholder) { + width: 150px; + padding-left: 48px; + color: #333; +} +.search-box .search-box input:not(:-moz-placeholder) { + width: 150px; + padding-left: 48px; + color: #333; +} +.search-box .search-box input::not(:-moz-placeholder) { + width: 150px; + padding-left: 48px; + color: #333; +} +.search-box .search-box input:not(:-webkit-input-placeholder) { + width: 150px; + padding-left: 48px; + color: #333; +} +.search-box .search-box input::not(:-webkit-input-placeholder) { + width: 150px; + padding-left: 48px; + color: #333; +} +.box { + width: 80%; /*changed to width to prevent resizing during search */ + flex-direction: column; + display: flex; + justify-content: center; +} +.watch-table { + width: 100%; +} +#header { + text-align: center; + vertical-align: middle; + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome, Edge, Opera and Firefox */ +} +#add-paused > label { + color: #fff; + font-weight: normal; +} +#play-pause { + line-height: .8em; +} +#checkbox-functions .pure-button { + background: #0078e7; +} +.watch-table td.title-col, #actions { + /*text-align: left;*/ + text-align: center; +} +.watch-table #clickable { + cursor: pointer; +} +.watch-table #sortarrow { + color: #0078e7; +} +.watch-table #hidden { + display: none; +} +.watch-table tr.unviewed { + font-weight: bold; +} +.watch-table .error { + color: #a00; +} +.watch-table td { + font-size: 80%; + white-space: nowrap; + text-align: center; +} +.watch-table td.title-col { + word-break: break-all; + white-space: normal; +} +.watch-table th { + white-space: nowrap; +} +.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-tag-list { + color: #e70069; + white-space: nowrap; +} +#watch-actions { + display: inline-block; +} +.box { + max-width: 80%; + flex-direction: column; + display: flex; + justify-content: center; +} +#post-list-buttons, #post-list-buttons-top { + + padding: 0; + margin: 0; +} +#post-list-buttons li, #post-list-buttons-top li { + display: inline-block; +} +#flexlayout { + display: flex; + justify-content: space-between; +} +#categories { + text-align: left; + margin-top: auto; +} +#controls-top { + text-align: right; + margin-top: auto; +} +#recheck-and-rss { + display: inline-flex; +} +#controls-bottom { + text-align: right; + margin-bottom: auto; + direction: rtl; +} +.watch-table tfoot { + bottom: 0; + position: fixed; +} +#post-list-buttons-top a { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-bottom-left-radius: initial; + border-bottom-right-radius: initial; +} +#post-list-buttons a { + border-top-left-radius: initial; + border-top-right-radius: initial; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +body:after { + content: ""; + background: linear-gradient(130deg, #ff7a18, #af002d 41.07%, #319197 76.05%); +} +body:after, +body:before { + display: block; + height: 600px; + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: -1; +} +body::after { + opacity: 0.91; +} +body::before { + content: ""; + background-image: url(../images/gradient-border.png); +} +body:before { + background-size: cover; +} +body:after, +body:before { + -webkit-clip-path: polygon( + 100% 0, + 0 0, + 0 77.5%, + 1% 77.4%, + 2% 77.1%, + 3% 76.6%, + 4% 75.9%, + 5% 75.05%, + 6% 74.05%, + 7% 72.95%, + 8% 71.75%, + 9% 70.55%, + 10% 69.3%, + 11% 68.05%, + 12% 66.9%, + 13% 65.8%, + 14% 64.8%, + 15% 64%, + 16% 63.35%, + 17% 62.85%, + 18% 62.6%, + 19% 62.5%, + 20% 62.65%, + 21% 63%, + 22% 63.5%, + 23% 64.2%, + 24% 65.1%, + 25% 66.1%, + 26% 67.2%, + 27% 68.4%, + 28% 69.65%, + 29% 70.9%, + 30% 72.15%, + 31% 73.3%, + 32% 74.35%, + 33% 75.3%, + 34% 76.1%, + 35% 76.75%, + 36% 77.2%, + 37% 77.45%, + 38% 77.5%, + 39% 77.3%, + 40% 76.95%, + 41% 76.4%, + 42% 75.65%, + 43% 74.75%, + 44% 73.75%, + 45% 72.6%, + 46% 71.4%, + 47% 70.15%, + 48% 68.9%, + 49% 67.7%, + 50% 66.55%, + 51% 65.5%, + 52% 64.55%, + 53% 63.75%, + 54% 63.15%, + 55% 62.75%, + 56% 62.55%, + 57% 62.5%, + 58% 62.7%, + 59% 63.1%, + 60% 63.7%, + 61% 64.45%, + 62% 65.4%, + 63% 66.45%, + 64% 67.6%, + 65% 68.8%, + 66% 70.05%, + 67% 71.3%, + 68% 72.5%, + 69% 73.6%, + 70% 74.65%, + 71% 75.55%, + 72% 76.35%, + 73% 76.9%, + 74% 77.3%, + 75% 77.5%, + 76% 77.45%, + 77% 77.25%, + 78% 76.8%, + 79% 76.2%, + 80% 75.4%, + 81% 74.45%, + 82% 73.4%, + 83% 72.25%, + 84% 71.05%, + 85% 69.8%, + 86% 68.55%, + 87% 67.35%, + 88% 66.2%, + 89% 65.2%, + 90% 64.3%, + 91% 63.55%, + 92% 63%, + 93% 62.65%, + 94% 62.5%, + 95% 62.55%, + 96% 62.8%, + 97% 63.3%, + 98% 63.9%, + 99% 64.75%, + 100% 65.7% + ); + clip-path: polygon( + 100% 0, + 0 0, + 0 77.5%, + 1% 77.4%, + 2% 77.1%, + 3% 76.6%, + 4% 75.9%, + 5% 75.05%, + 6% 74.05%, + 7% 72.95%, + 8% 71.75%, + 9% 70.55%, + 10% 69.3%, + 11% 68.05%, + 12% 66.9%, + 13% 65.8%, + 14% 64.8%, + 15% 64%, + 16% 63.35%, + 17% 62.85%, + 18% 62.6%, + 19% 62.5%, + 20% 62.65%, + 21% 63%, + 22% 63.5%, + 23% 64.2%, + 24% 65.1%, + 25% 66.1%, + 26% 67.2%, + 27% 68.4%, + 28% 69.65%, + 29% 70.9%, + 30% 72.15%, + 31% 73.3%, + 32% 74.35%, + 33% 75.3%, + 34% 76.1%, + 35% 76.75%, + 36% 77.2%, + 37% 77.45%, + 38% 77.5%, + 39% 77.3%, + 40% 76.95%, + 41% 76.4%, + 42% 75.65%, + 43% 74.75%, + 44% 73.75%, + 45% 72.6%, + 46% 71.4%, + 47% 70.15%, + 48% 68.9%, + 49% 67.7%, + 50% 66.55%, + 51% 65.5%, + 52% 64.55%, + 53% 63.75%, + 54% 63.15%, + 55% 62.75%, + 56% 62.55%, + 57% 62.5%, + 58% 62.7%, + 59% 63.1%, + 60% 63.7%, + 61% 64.45%, + 62% 65.4%, + 63% 66.45%, + 64% 67.6%, + 65% 68.8%, + 66% 70.05%, + 67% 71.3%, + 68% 72.5%, + 69% 73.6%, + 70% 74.65%, + 71% 75.55%, + 72% 76.35%, + 73% 76.9%, + 74% 77.3%, + 75% 77.5%, + 76% 77.45%, + 77% 77.25%, + 78% 76.8%, + 79% 76.2%, + 80% 75.4%, + 81% 74.45%, + 82% 73.4%, + 83% 72.25%, + 84% 71.05%, + 85% 69.8%, + 86% 68.55%, + 87% 67.35%, + 88% 66.2%, + 89% 65.2%, + 90% 64.3%, + 91% 63.55%, + 92% 63%, + 93% 62.65%, + 94% 62.5%, + 95% 62.55%, + 96% 62.8%, + 97% 63.3%, + 98% 63.9%, + 99% 64.75%, + 100% 65.7% + ); +} +.arrow { + border: solid #000; + border-width: 0 3px 3px 0; + display: inline-block; + padding: 3px; +} +.arrow.right { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} +.arrow.left { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} +.arrow.up { + transform: rotate(-135deg); + -webkit-transform: rotate(-135deg); +} +.arrow.down { + transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} +.button-small { + font-size: 85%; +} +.fetch-error { + padding-top: 1em; + font-size: 60%; + max-width: 400px; + display: block; +} +.button-secondary { + color: #fff; + border-radius: 4px; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); +} +.button-success { + background: #1cb841; +} +.button-tag { + background: #636363; + color: #fff; + font-size: 65%; + border-bottom-left-radius: initial; + border-bottom-right-radius: initial; +} +.button-tag.active { + background: #9c9c9c; + font-weight: bold; +} +.button-error { + background: #ca3c3c; +} +.button-warning { + background: #df7514; +} +.button-secondary { + background: #42b8dd; +} +.button-cancel { + background: #c8c8c8; +} +.messages li { + list-style: none; + padding: 1em; + border-radius: 10px; + color: #fff; + font-weight: bold; +} +.messages li.message { + background: rgba(255, 255, 255, 0.2); +} +.messages li.error { + background: rgba(255, 1, 1, 0.5); +} +.messages li.notice { + background: rgba(255, 255, 255, 0.5); +} +#notification-customisation { + border: 1px solid #ccc; + padding: 1rem; + border-radius: 5px; +} +#token-table.pure-table td, +#token-table.pure-table th { + font-size: 80%; +} +#new-watch-form { + background: rgba(0, 0, 0, 0.05); + padding: 1em; + border-radius: 10px; + margin-bottom: 1em; +} +#new-watch-form input { + width: auto !important; + display: inline-block; +} +#new-watch-form .label { + display: none; +} +#new-watch-form legend { + color: #fff; +} +#diff-col { + padding-left: 40px; +} +#diff-jump { + position: fixed; + left: 0; + top: 120px; + background: #fff; + padding: 10px; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + box-shadow: 5px 0 5px -2px #888; +} +#diff-jump a { + color: #1b98f8; + cursor: grabbing; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + -o-user-select: none; +} +footer { + padding: 10px; + background: #fff; + color: #444; + text-align: center; +} +#feed-icon { + vertical-align: middle; +} +.sticky-tab { + position: absolute; + top: 80px; + font-size: 11px; + background: #fff; + padding: 10px; +} +.sticky-tab#left-sticky { + left: 0; +} +.sticky-tab#right-sticky { + right: 0; +} +#new-version-text a { + color: #e07171; +} +.paused-state.state-False img { + opacity: 0.2; +} +.paused-state.state-False:hover img { + opacity: 0.8; +} +.monospaced-textarea textarea { + width: 100%; + font-family: monospace; + white-space: pre; + overflow-wrap: normal; + overflow-x: scroll; +} +.pure-form .pure-control-group, +.pure-form .pure-group, +.pure-form .pure-controls { + padding-bottom: 1em; +} +.pure-form .pure-control-group div, +.pure-form .pure-group div, +.pure-form .pure-controls div { + margin: 0; +} +.pure-form .error input { + background-color: #ffebeb; +} +.pure-form ul.errors { + padding: 0.5em 0.6em; + border: 1px solid #d00; + border-radius: 4px; + vertical-align: middle; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.pure-form ul.errors li { + margin-left: 1em; + color: #d00; +} +.pure-form label { + font-weight: bold; +} +.pure-form textarea { + width: 100%; +} +.pure-form ul#fetch_backend { + margin: 0; + list-style: none; +} +.pure-form ul#fetch_backend > li > * { + display: inline-block; +} +@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px) { + .box { + max-width: 95%; + } + .watch-table td.low-res { + display: inline-block; + padding: 5px; + } + .search-box input[type=text]:focus,input[type=text]:not(:placeholder-shown) { + width: 150px; + } + .pure-form fieldset { + padding: 0; + } + .edit-form { + padding: 0.5em; + margin: 0; + } + #nav-menu { + overflow-x: scroll; + } + .hover-img img { + opacity: 1; + } + .hover-img:hover img { + opacity: 1; + } + #watch-actions { + margin-top: 3px; + } + #checkbox-functions { + display: inline-block; + } +} +@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px) { + input[type="text"] { + width: 100%; + } + .search-box input { + width: 32px; + } + .watch-table thead, + .watch-table tbody, + .watch-table th, + .watch-table td, + .watch-table tr { + display: block; + } + .watch-table .last-checked::before { + color: #555; + content: "Checked "; + } + .watch-table .last-changed::before { + color: #555; + content: "Changed "; + } +/* .watch-table td.inline { + display: inline-block; + } + .watch-table thead tr { + position: absolute; + top: -9999px; + left: -9999px; + }*/ + .watch-table th:nth-child(0) { + display: none; + } + .watch-table th:nth-child(1) { + display: none; + } + .watch-table th:nth-child(2) { + display: none; + } + .watch-table th:nth-child(3) { + display: none; + } + .watch-table th:nth-child(4) { + display: none; + } + .watch-table th:nth-child(5) { + display: inline-block; + } + .watch-table th:nth-child(6) { + display: none; + } + .watch-table th:nth-child(7) { + display: inline-block; + } + .watch-table th:nth-child(8) { + display: none; + } + .watch-table th:nth-child(9) { + display: inline-block; + } + .watch-table th:nth-child(10) { + display: none; + } + .watch-table th:nth-child(11) { + display: none; + } + .watch-table .pure-table td, + .watch-table .pure-table th { + border: none; + } + .watch-table td { + border: none; + border-bottom: 1px solid #eee; + } + .watch-table td:before { + top: 6px; + left: 6px; + width: 45%; + padding-right: 10px; + white-space: nowrap; + } + .watch-table.pure-table-striped tr { + background-color: #fff; + } + .watch-table.pure-table-striped tr:nth-child(2n-1) { + background-color: #eee; + } + .watch-table.pure-table-striped tr:nth-child(2n-1) td { + background-color: inherit; + } +} +@media only screen and (min-width: 761px) { + .m-d { + min-width: 80%; + } +} +.tabs ul { + margin: 0; + padding: 0; + display: block; +} +.tabs ul li { + margin-right: 3px; + display: inline-block; + color: #fff; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background-color: rgba(255, 255, 255, 0.2); +} +.tabs ul li.active, +.tabs ul li :target { + background-color: #fff; +} +.tabs ul li.active a, +.tabs ul li :target a { + color: #222; + font-weight: bold; +} +.tabs ul li a { + display: block; + padding: 0.8em; + color: #fff; +} +.pure-form-stacked > div:first-child { + display: block; +} +.edit-form { + min-width: 70%; +} +.edit-form .tab-pane-inner { + padding: 0; +} +.edit-form .tab-pane-inner:not(:target) { + display: none; +} +.edit-form .tab-pane-inner:target { + display: block; +} +.edit-form .box-wrap { + position: relative; +} +.edit-form .inner { + background: #fff; + padding: 20px; +} +.edit-form #actions { + display: block; + background: #fff; +} diff --git a/changedetectionio/templates/base.html b/changedetectionio/templates/base.html index d4533fde..5bd1a030 100644 --- a/changedetectionio/templates/base.html +++ b/changedetectionio/templates/base.html @@ -13,7 +13,7 @@ {% endfor %} {% endif %} - +
@@ -23,7 +23,7 @@ {% else %} ChangeDetection.io {% endif %} - {% if current_diff_url %} + {% if current_diff_url %} {{ current_diff_url }} {% else %} {% if new_version_available and not (has_password and not current_user.is_authenticated) %} @@ -34,6 +34,9 @@
    {% if current_user.is_authenticated or not has_password %} {% if not current_diff_url %} +
  • + +
  • BACKUP
  • diff --git a/changedetectionio/templates/watch-overview.html b/changedetectionio/templates/watch-overview.html index ba994b65..2c514226 100644 --- a/changedetectionio/templates/watch-overview.html +++ b/changedetectionio/templates/watch-overview.html @@ -2,99 +2,181 @@ {% block content %} {% from '_helpers.jinja' import render_simple_field %} + +
    -
    -
    - Add a new change detection watch - {{ render_simple_field(form.url, placeholder="https://...", required=true) }} - {{ render_simple_field(form.tag, value=active_tag if active_tag else '', placeholder="tag") }} - -
    - - -
    -
    - All - {% for tag in tags %} - {% if tag != "" %} - {{ tag }} - {% endif %} - {% endfor %} -
    - +
    +
    + Add a new change detection watch + {{ render_simple_field(form.url, placeholder="https://...", required=true) }} + {{ render_simple_field(form.tag, value=active_tag if active_tag else '', placeholder="tag") }} +
      +
    +
    + + +
    - - - - - - - - - - - - + +
    +
    #Last CheckedLast Changed
    + + + + + + + + + + + + + + + + + {% for watch in watches %} + + + + + + - - - - - - - - - {% endfor %} - -
    {{ loop.index }} + {% if watch.paused %} + Resume + {% else %} + Pause + {% endif %} + + {% if watch.newest_history_key| int > watch.last_viewed| int %} + Viewed + {% else %} + Unviewed + {% endif %} + {{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}} + + {%if watch.fetch_backend == "html_webdriver" %}{% endif %} - {% for watch in watches %} -
    {{ loop.index }}Pause{{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}} - - {%if watch.fetch_backend == "html_webdriver" %}{% endif %} - - {% if watch.last_error is defined and watch.last_error != False %} -
    {{ watch.last_error }}
    - {% endif %} - {% if not active_tag %} - {{ watch.tag}} - {% endif %} -
    {{watch|format_last_checked_time}}{% if watch.history|length >= 2 and watch.last_changed %} - {{watch.last_changed|format_timestamp_timeago}} - {% else %} - Not yet - {% endif %} - - Recheck - Edit - {% if watch.history|length >= 2 %} - Diff - {% else %} - {% if watch.history|length == 1 %} - Preview - {% endif %} - {% endif %} -
    - -
    -
    -{% endblock %} + {% if watch.last_error is defined and watch.last_error != False %} +
    {{ watch.last_error }}
    + {% endif %} + {% if not active_tag %} + {{ watch.tag}} + {% endif %} + + {{ watch.title if watch.title else watch.url }} + {{watch|format_last_checked_time}} + {{ watch.last_checked }} + {% if watch.history|length >= 2 and watch.last_changed %} + {{watch.last_changed|format_timestamp_timeago}} + {% else %} + Not yet + {% endif %} + + {{ watch.last_changed }} + + Recheck + Edit + {% if watch.history|length >= 2 %} + Diff + {% else %} + {% if watch.history|length == 1 %} + Preview + {% endif %} + {% endif %} + + + {% endfor %} + + +
+ + + + {% endblock %}